Fork me on GitHub
Fredrik Andersson10:02:53

I have worked for a while with shadow-cljs and I enjoy the watch workflow. Now I'm looking into Clojure, lacinia and pedestal. I wonder how I could setup the same workflow. Particularly when a file is changed I want a stop function followed by compiler and update and then start function again. Is this possible with clj and deps.edn?


Including the web application (typically the router function) as a reference means that the router function or the handler functions it calls can be evaluated to pick up changes without restarting the application server (e.g. jetty, httpkit, etc)

(app-server/run-server #'app {:port http-port})
Or the application server can be restarted ( using either an atom based reference, mount, integrant, etc.), typically providing start / stop / restart functions The browser showing a web page from the system would have to be manually refreshed, unless some library or function was added to automatically do this


To avoid restarting the repl when adding dependencies, add-libs can be used to 'hotload' one or more libraries

Fredrik Andersson13:02:47

Sorry, I was a little to eager. At the moment I'm using a user-namespace where I start the server when I start testing. Then I change my code, stop it via the user namespace, refresh changed namespaces with Conjure and then start the server again. I wish there were a solution that would stop, refresh and start each time I save a file.

Fredrik Andersson13:02:18

I'm not sure if you suggest my current workflow in your first suggestion?

Fredrik Andersson13:02:49

The browser refresh isn't important since that is done via shadow-cljs

Fredrik Andersson13:02:21

I'll watch the video


If only working in the one namespace, then evaluating the whole namespace should update functions for routing and handlers in the REPL, but nothing outside those functions. (you dont actually need to save the file to evaluate, but its recommended to keep things in sync) To reload everything, then clojure.namespace/refresh is probably the simplest (although mount, integrant, component, donut could be added for this too, but for just a web server that is probably overkill)


There is a discussion about reloading with Conjure in this thread

👍 1

Whey does below code giving 2 as output ? I expected value 4

(def the-world (ref "hello" :min-history 10))

  (dosync (ref-set the-world "better"))

(let [exclamator (fn [x] (str x "!"))]
   (alter the-world exclamator)
   (alter the-world exclamator)
   (alter the-world exclamator))

(ref-history-count the-world)


never really use refs and dosync during my 7 years of programming using clojure! because most of time, you need a database to coordinate the data, other than using refs in memory! most of the condition, atom is enough to define variable, and probably many condition, your’d using core.async channel and go thread to sequencial the event other than treat it as transactional, that’s useless most of the time


yeah I agree,


or is this, consider one value in dosync ?


> All changes made to Refs during a transaction (via ref-set, alter or commute) will appear to occur at a single point in the 'Ref world' timeline (its 'write point'). > from here

👀 1

Haven't had need to use refs myself. So @U04V4KLKC's response did not seem all that helpful to me... Until I looked up the docs for dosync, (which I also never needed to use), where I learned that dosync is what makes this a transaction. > (dosync & exprs) > Runs the exprs (in an implicit do) in a transaction that encompasses exprs and any nested calls. ... > So dosync is creating a transaction context. All the expressions run in that context are applied as a single all-or-nothing transaction. Then as @U04V4KLKC pointed out, the whole transaction is recorded as a single write point.


Just trying out few things in clojure repl today , Why repl is not ending execution after running (execute-tasks)
(def task-queue (async/chan 4))

(defn send-task [ & tasks]
  (doseq [task tasks] 
         (println "Adding task to the queue "task)
         (async/>!! task-queue task )))

(defn execute-tasks []
  (loop [] 
    (when-some [task (async/<!! task-queue)]
      (println (eval task))

  (send-task `(+ 1 2))
  (send-task `(+ 1 2))
  (send-task `(+ 1 2))
  (send-task `(+ 1 2))


probably it is going in infinite loop ?


(when-some [task (async/<!! task-queue)] will block forever trying to take from the task queue. You need to close the task when it is done, or put a value on the task queue to indicate to stop taking more


is there any way where I can check all task is in queue is removed > so that I can cam close the channel at that time


you can close the channel when you know no more items will be put on the queue


user=> (defn execute-tasks []
    (println "starting task running on background thread")
    (loop []
      (if-some [task (async/<!! task-queue)]
          (println (eval task))
        (println "stopping task running loop")))))

user=> (execute-tasks)
#object[clojure.core$future_call$reify__8544 0x59fa5a39 starting task running on background thread
{:status :pending, :val nil}]
user=> (send-task `(+ 1 2))
Adding task to the queue  (clojure.core/+ 1 2)
user=> 3
(send-task `(+ 1 2))
Adding task to the queue  (clojure.core/+ 1 2)

user=> (async/close! task-queue)
user=> stopping task running loop


Couldn't find a better channel for this, but I'm trying out Selmer and having issues with keywords. I have them namespaced, like so: and I'm trying to reach them in a template with {{}} and it's not finding them. I can see from the docs and confirm in experiments that the template can find . I could write a converter function, but that's just avoiding the issue.

Hans Lux19:02:59

Confusion about namespaced keywords and spec. I heard it's good to use namespaded keywords in my maps. Say I have a person model with fname and lname. Should the model be

::fname fname
::lname lname
or rather
:person/fname fname
:person/lname lname
And how do I write specs for that? For example: this implementation:
(def non-empty-string? #(not (str/blank? %)))

(s/def :person/fname

(s/def :person/lname

(s/def :person/person
  (s/keys :req-un [:person/fname

(defn make-person [fname lname]
  (s/assert :person/person
            {:person/fname fname
             :person/lname lname}))
passes this test
(deftest make-person-test
  (testing "creating a person"
    (are [fname lname person] (= (make-person fname lname) person)
                              "Fname" "Lname" {:person/fname "Fname" :person/lname "Lname"}
                              "Fname2" "Lname2" {:person/fname "Fname2" :person/lname "Lname2"}
However, when I try to create a 'person' in the repl , I get an error:
=> #object[clojure.lang.Namespace 0x768bdf96 "user"]
(people-i-know.model.person/make-person "Fname" "Lname")
Execution error - invalid arguments to people-i-know.model.person/make-person at (person.clj:17).
#:person{:fname "Fname", :lname "Lname"} - failed: (contains? % :fname)
#:person{:fname "Fname", :lname "Lname"} - failed: (contains? % :lname)
Obviously there is something about namespaces and specs I haven't understood.


The un in req-un means unqualified (no namespace) so your person spec is for a map with the keys :fname and :blame, which is why the spec fails, and why it reports that your map doesn't contain those keys


I'm not sure about the test part, but your spec is failing because the keys are specified with :req-un and make-person is using qualified (ie. namespaced) keywords

Hans Lux19:02:32

you are both right! 🙂 I somehow had req-un(ion) in my mind and thought it meant to apply all specs for the keys. Thank you very much for your quick and correct answer. It is working now.


Also, I would probably recommend making an explicit spec for the non-empty string:

(s/def ::non-empty-string?
   #(not (str/blank? %))))

(s/def :person/fname ::non-empty-string?)


this should make it possible to generate persons.

Hans Lux20:02:49

would you do that to make it more clear to the eye or also for technical reasons?


I think either could be fine depending on the use case and personal preference.


It's helpful if you (or someone who uses your specs) wants to do generative testing.


or generate examples


I think it might also slightly improve the output when explaining why a particular value doesn't conform.

Hans Lux20:02:59

thank you for that hint. I'm planning to look into generative testing, but it seems that I have to do a bit more reading on specs before.

👍 1