3

I'm using a multimethod to provide different functions depending on what "mode" my project is running in (it's a yada api server, and should be able to run in :dev, :prod modes etc).

I'm using mount/defstate to provide a keyword:

(defstate mode :start :dev)

When I dispatch using (constantly mode), I get errors but when I dispatch using (fn [& _] mode) it seems to work.

Aren't these two forms the same? Or is there some subtle difference in the way in which (or the time at which) they are evaluated?

nha
  • 17,623
  • 13
  • 87
  • 133
Jeremy Field
  • 652
  • 7
  • 12

2 Answers2

5

In mount if you have not started your states yet then their values are DerefableState objects.

By calling constantly you first evaluate the value of mode and then call the constantly function with the value. This means that when you invoke the result of constantly it will always return the parameter of constantly despite the fact that you have changed it since. If you have not started your state before calling constantly then it will store the DerefableObject.

On the other hand with (fn [& _] mode) you evaluate the value of the mode var every time you call the function. If you have not started your states then it will also return a DerefableState but if you have then the result will be the expected keyword.

A simple solution is to put the dispatch function in a state too.

(defstate dispatch :start (constantly state))
erdos
  • 3,135
  • 2
  • 16
  • 27
  • Thank you, great explanation. Is it correct to say the `constantly` form closes over `mode`, as it exists at compile time, whereas the `fn` form evaluates `mode` at runtime? – Jeremy Field Jul 21 '18 at 05:32
  • 1
    `constantly` closes over `mode` not in compile time but the time it is invoked during runtime. – erdos Jul 21 '18 at 06:58
  • I tried putting dispatch into a `defstate` like you recommended, but am getting `mount.core.DerefableState cannot be cast to clojure.lang.IFn` from the `(defmulti fn-name dispatch)` line. I think I understand why this is happening. Did I misunderstand your suggestion? – Jeremy Field Jul 22 '18 at 01:35
  • hmm, it depends on how you use `dispatch` but probably the same as above: you use its value before Mount is started. Try using `#'dispatch` instead of `dispatch` maybe? – erdos Jul 22 '18 at 08:15
  • I settled on a semi-workaround: https://gist.github.com/jdf-id-au/419c83f119bad7b4981a168ab6d37b78 – Jeremy Field Jul 22 '18 at 09:55
3

I think for an alternate explanation, you could use side effects to highlight what the difference is.

Compare:

(def const-f (constantly (println "Hello!")))
Hello!
=> #'user/const-f

(def fn-f (fn [] (println "World!")))
=> #'user/fn-f

Just executing the first def causes Hello! to be printed, since the body of constantly is evaluated immediately. The second def however prints nothing since the body of the fn isn't evaluated.

When calling them though:

(const-f)
=> nil ; Prints nothing. Just evaluates to what println returned

(fn-f)
World! ; Prints now,
=> nil ;  then returns what println evaluates to

constantly isn't a macro, so it's arguments must be evaluated first. fn however is a macro, so it runs before its arguments are evaluated.

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117