Fork me on GitHub
#clojure
<
2023-05-14
>
Drew Verlee02:05:49

My mutable state is out of control. No, seriously, I'm using https://github.com/stuartsierra/component, and when I update a component (e.g (start ... (prn "1")...) to (start ..(prn "2")...) , the update (1->2) doesn't take place when i call (reset). Hopefully the names ma I created a playground where i just messed with simple components unrelated to my app and was unable to re-create the issue. To make it even stranger calling reset once will in fact capture the change, but calling it again doesn't. Professional speaking, I would like to provide a very concise bug report, but given I can't create a toy version of the issue and I can't dump my company code in chat, I'm not sure how to proceed. I'll put the playground code in a thread with an explanation of what's going wrong in the real app (but again, not the playground app)

Drew Verlee02:05:03

(ns app.core
  (:require [com.stuartsierra.component :as component]
            [clojure.tools.namespace.repl :refer (refresh)]))

(defn connect-to-database [host port] "blah")

(defrecord Database [host port connection]
  component/Lifecycle

  (start [component]
    (println "UPDATE1")
    (let [conn (connect-to-database host port)]
      (assoc component :connection conn)))

  (stop [component]
    (assoc component :connection nil)))

(defn new-database [host port]
  (map->Database {:host host :port port}))

(defn get-user [db arg]  "joe")

(defrecord ExampleComponent [options cache database scheduler]
  component/Lifecycle

  (start [this]
    (assoc this :admin (get-user database "admin")))

  (stop [this]
    this))

(defn example-component [config-options]
  (component/using
    (map->ExampleComponent {:options config-options
                            :cache (atom {})})
    [:db]))

(defn example-system [config-options]
  (let [{:keys [host port]} config-options]
    (-> (component/system-map
         :config-options config-options
         :db (new-database host port)
         :app (example-component config-options))
        (component/system-using
          {}))))

(def system nil)

(defn init []
  (alter-var-root #'system
    (constantly (example-system {:host "" :port 123}))))

(defn start []
  (alter-var-root #'system component/start))

(defn stop []
  (alter-var-root #'system
    (fn [s] (when s (component/stop s)))))

(defn go []
  (init)
  (start))

(defn reset []
  (stop)
  (refresh :after 'app.core/go))

;; (go)

;; change "UPDATE1" to UPDATE2 eval record

;;(reset)


;; it prints UPDATE1 as expected
;; change "UPDATE2" to UPDATE3 eval record
;;(reset)

;; in this toy example could it will print UPDATE3. In the app with the issue it prints UPDATE2 where i expected UPDATE3

mdiin08:05:47

Out of curiosity, what happens if you do not define the start and stop functions directly on the defrecords? Something like

(defn start-db [db] …)

(defrecord DB
  lifecycle/start #’start-db)
Redefining start-db should then be reflected even on existing records. I can’t see why it would not pick up as it is, given your example, but I remember having had trouble redefining records previously. Some suggestion might be better than none. 😅

emccue14:05:45

Same response as mdiin, but if you think the records are the problem, try replacing them with metadata

Drew Verlee16:05:26

I think it's a problem detecting the change, the first time i reset, when it correctly finds it, a lot more files are reloaded then when it fails. I'm trying to build up the system map piece by piece to see when it stops working. I feel like this is one of the hardest parts of clojure, it's the tdd of our language.

Eugen05:05:12

I think using the metadata version of components will be clearer

Drew Verlee19:05:03

@U0522J5HN @U3JH98J4R @U011NGC5FFY Thanks for the feedback. After some further searching, it seem's I'm not the first person to be confused by the behavior I'm getting. Here is an http://Hi%20friend,%20I%20appreciate%20your%20efforts%20in%20Donut%20to%20make%20a%20"less%20magical"%20DI%20system.%20I'm%20currently%20experiencing%20some%20"magic"%20that%20i%20was%20hoping%20you%20could,%20despite%20no%20obvious%20benefit%20to%20yourself%20:confused:%20,%20could%20help%20me%20understand.%20The%20confusion%20is%20best%20captured%20in%20this%20github%20issue.%20%20%20And%20my issue that captures the behavior. https://github.com/weavejester/reloaded.repl/issues/12 Right or wrong, it caught me off guard. I added a comment that tried to get close to the thing i didn't understand. Of course, the things you don't understand, are those things you have the most trouble explaining! If anyone see's the larger picture here and wants to show me the light, i would appreciate it.

isak23:05:17

I'm guessing the confusion may be from vars. When does the value of the var get derefenced? Consider this example:

(defn my-inc [n]
  (println "Update 2")
  (inc n))

(def my-inc-user
  (let [my-inc' my-inc]
    (fn []
      (my-inc' 5))))

(defn my-inc-user-2 []
  (my-inc 5))

(comment
  (my-inc-user)
  
  (my-inc-user-2 )
  )
Try updating the 'my-inc' function then calling the two 'user' functions and you'll see the difference.

Drew Verlee00:05:33

I assume my-inc-user won't get updated because it's closed over in a def. Ill have to try at my repl. I think clojure tools namespace repl might cause it to update though.

vncz15:05:09

I remember there was a very good article on the web explaining how to use references, quotes to reload functions in REPL. Does anybody remember or have a link around?

seancorfield16:05:08

Do you mean #' as a prefix, so you get Var references and can redefine functions and have their new value picked up immediately?

emccue17:05:26

i wrote about this a little bit

vncz18:05:47

That’s that I mean; but I am confident it was not on the Clojure official website. I will take a look!

emccue18:05:03

sorry forgot to link

emccue18:05:24

ctrl+f for indirection

vncz18:05:53

@U3JH98J4R Yeah I believe it was that piece!

emccue21:05:49

i should write a part 2 at some point

👀 6
vncz15:05:31

Great. Plenty of resources. Thanks!