Fork me on GitHub
#beginners
<
2023-03-06
>
keychera07:03:56

I am having difficulty how to structure a configurable values in a project suppose that initially I have a top-level def that is used inside several defn like this

(def port 4242)
(defn swap-body! []
  (str "using the value " port))

(defn -main [& _] #_swap-body!-used-here)
and the later in the development, I decided to make this port value configurable through -main args, and to make that happen, I only know these options: • make the port atom • add parameter port to swap-body! • define swap-body! inside the -main body like this
(defn -main [port]
  (let [swap-body! (fn [] (str "using the value " port))] 
     #_swap-body!-used-here))
but I feel like all of these options are awkward. I have more things that I want to be configurable at the initialization and not just limited to static values (like predicates fn for example). I feel like the option 1 will be the least obstrusive change if I make one big atom containing all the config, but I was not so sure so currently I am using option 3 since I only need to move the definition, Does anyone have advice?

rolt09:03:07

it's usually option 1 or option 2. Option 1 gives you ease of use, option 2 give you more control (it allows you spawning several servers for instance). You can check out https://github.com/grammarly/omniconf if you go with option 1

delaguardo09:03:16

there is also an option to make port var dynamic and give it a binding in -main

(def ^:dynamic port 4242)

(defn do-some-work []
  (println port))

(defn -main [custom-port]
  (binding [port custom-port]
    (do-some-work)))

phill10:03:17

Option 2 scales well and accommodates gracefully if you decide to fire up a server on a random free port for unit tests. Gathering all such settings in a map like {:port 4242 :etc :etc} is a harmless expedient.

keychera02:03:23

Thank you everyone for all the input! binding definitely went under my radar, I think for my smaller project, I’m probably be using that while I need to try some options for my bigger ones

martin11:03:17

Hello! I have a question, I am clearly missing something. I going to try to explain my question the best I can. In this example i'm storing an input value :selected-time in an atom select-time-state, that I'm defining in a let in the function select-time. When I do this - i get the set value "10" in my app, BUT i can not change it on the webpage - even if I have an :on-changehandler. BUT - if I just def the atom outside of select-time (the "hidden" defonce - and remove the let ofc) - everything works as expected. So my question is - why dosen't it allow me to change the state of the atom in the webpage if I def the atom inside the function with let?

#_(defonce select-time-state (atom {:selected-time "10"}))
  
  (defn select-time []
    (let [select-time-state (atom {:selected-time "10"})]
         [:div.row
          [:div.col-4.center
           [:input {:type "text"
                    :placeholder "Select time"
                    :value (:selected-time @select-time-state)
                    :on-change #(swap! select-time-state assoc :selected-time (-> % .-target .-value))}]
           @select-time-state]]))
  
  (defn countdown-complete []
    [:div.row
     [:div.col.center
      [:h1 "Select the time"]]
     [select-time]])

pez13:03:59

I think this is because the component is re-rendered and the let binding re-evaluated. Or something. Assuming this is reagent. Reagent has three types of components: Form 1, 2, 3, where both 2 and 3 allows you to bind outside the actual component vector, making them act more like your defonce.

pez13:03:35

Also, you probably want a reagent atom there, which is reactive.

martin14:03:16

Yes, it is reagent! The atom is also a reagent atom. (:require [reagent.core :as reagent :refer [atom]])

pez14:03:25

I see. I would do (:require [reagent.core :as r]) and (let [foo (r/atom :foo)]). So that you easily can use regular atoms and reactive ones in the same namespace. And it is also easier to see on a snippet of code what kind of atom is being used.

2
pez14:03:10

Here you can read about the 3 different ways of creating reagent components: https://github.com/reagent-project/reagent/blob/master/doc/CreatingReagentComponents.md

pez14:03:28

All this said, I often prefer using defonce for component state over let. It's much more REPL-friendly.

2
pez14:03:03

And then most often I go for re-frame. But baby steps. 😃

🙌 2
martin14:03:23

Thank you! 😄 Much appreciated!

🙏 2
Sam b17:03:19

Clojure newb here, trying/failing to set-up a simple server Steps taken: 1. Install deps with clj 2. Open new terminal cd into src 3. run clj -M core.clj Error Syntax error macroexpanding clojure.core/ns at (core.clj:1:1). ((:require ring.jetty.adapter :as jetty)) - failed: Extra input spec: :clojure.core.specs.alpha/ns-form deps.edn

{:paths ["src"]
 :deps
  {org.clojure/clojure {:mvn/version "1.11.1"}
   ring/ring-core {:mvn/version "1.9.6"}
   ring/ring-jetty-adapter {:mvn/version "1.9.6"}}}
  
src/core.clj
(ns core
  (:require ring.jetty.adapter :as jetty)) 

  (defn handler [request]
    {:status 200
     :headers {"Content-Type" "text/html"}
     :body "welcome aboard"}) 

  (defn -main []
    (jetty/run-jetty handler {:port 3000
                        :join? false}))

Grigory Shepelev17:03:19

You use require not properly.

Grigory Shepelev17:03:49

(:require [...])

2
Grigory Shepelev17:03:34

`(:require [...] [...])`

2
Sam b17:03:28

I fixed the initial error following your example. I now have this new error:

Execution error (FileNotFoundException) at servee/eval138$loading (servee.clj:1).
Could not locate ring/jetty/adapter__init.class, ring/jetty/adapter.clj or ring/jetty/adapter.cljc on classpath.

dpsutton17:03:12

what is your current working directory where you are running clj -M ... ?

Sam b18:03:02

project/src/

dpsutton18:03:37

stay in project

dpsutton18:03:44

run from the root of your project

Sam b18:03:38

Running it from the root dir, I get: Execution error (FileNotFoundException) at .FileInputStream/open0 (FileInputStream.java:-2). core.clj (No such file or directory)...

dpsutton18:03:53

clj -M -m core is the invocation you want. You also need to change • bad: [ring.jetty.adapter :as jetty] • good: [ring.adapter.jetty :as jetty]

2
dpsutton18:03:20

From clj --help > main-opt: > -m, --main ns-name Call the -main function from namespace w/args This is expecting a namespace name (`ns-name`). So we want to pass it core and not a filename core.clj

2
Sam b18:03:55

@U11BV7MTK & @U04R4718WAJ. Thank you for the support & patience, I now have a working server 🎉🎉

🎉 6