mount

arnout 2017-07-11T08:23:31.304877Z

Hi @onetom, I see your problem. I think it can be solved using:

(let [started (promise)]
  (state :start @(deliver started (my-start-expr))
         :stop (my-stop-expr @started)))
But that is not really nice. Maybe we could bind the result of the start expression to something? Any suggestions?

onetom 2017-07-11T08:26:22.369888Z

(stop (state :start 123 :stop (prn %))) could print 123... 😕

onetom 2017-07-11T08:28:56.428968Z

intuitively i was expecting state to handle the value of the stop parameter specially if it's a function

arnout 2017-07-11T08:28:56.429067Z

Hmm, that may be a bit too implicit, and could also clash with a "proper" % in the stop expression..

onetom 2017-07-11T08:29:15.436470Z

err... what is a "proper" %?

arnout 2017-07-11T08:29:43.447216Z

(state :start ... :stop (map #(do-stuff %) things))

arnout 2017-07-11T08:30:28.464634Z

Here the % is used by the anonymous function, and should not be bound to the result of the :start expression.

onetom 2017-07-11T08:31:36.490414Z

(if start
    `(#'map->State (merge ~(dissoc fields :start :stop :name)
                          {:start-fn (fn [] ~start)
                           :stop-fn  (fn [] ~stop)
                           :name     ~name}))
    (throw (ex-info "missing :start expression" {})))

onetom 2017-07-11T08:32:31.511208Z

so this is the state macro... what if the :stop-fn would receive the value of the current state by default?

onetom 2017-07-11T08:33:05.523686Z

like :stop-fn (fn [*state*] ~stop)

onetom 2017-07-11T08:34:17.550836Z

it's a bit automagical, i agree, but is there a reason why stopping is not done like that?

onetom 2017-07-11T08:37:56.633047Z

as u can tell im kinda stumbling in the dark, because i've only used component & danielsz/system and only looked into mount and mount-lite seriously in the past ~3 days. im also confused by the slight differences between the two and i also went thru their evolution once again in just 3 days. i read all the latest commits between the lastest releases and the latest snapshot versions in both repos. so forgive me if im mixing things up a bit or don't make much sense 🙂

arnout 2017-07-11T08:38:36.648512Z

No problem, you have a valid point

onetom 2017-07-11T08:39:08.660053Z

on top of that i've only used boot in the past ~4years of my clojure career and im just learning lein, which means there is suddenly no more boot.pods; everything is under one classpath...

onetom 2017-07-11T08:44:05.774345Z

have you personally used the (with-session ...) construct in your projects? it would be good if you could show some examples how does it look like for u.

onetom 2017-07-11T08:51:58.959744Z

im also confused a bit by your recommendations in the Design consideration section of the docs. > Having a global state does not mean you should forego on the good practice of passing state along as arguments to functions. seems to contradict this statement: > Try to use your defstate as if it were private. Better yet, declare it as private. This will keep you from refering to your state from every corner of your application, making it more componentized. if my state is private, then it means i have some functions implicitly using it, no? like the tx!, db & q functions in my example: https://gitlab.com/onetom/mount-lite-example/blob/master/src/app/db.clj#L13-15

arnout 2017-07-11T08:53:58.009093Z

I haven't used boot a lot, so I can't really say something about that, as far as it is related to mount(-lite) 🙂

arnout 2017-07-11T08:55:17.040337Z

In the end the project that inspired us to create version 2 of mount-lite did not require multiple sessions after all, so no, we have not used with-session a lot.

arnout 2017-07-11T08:55:37.048258Z

What I mean in the documentation, is as follows:

(defn- do-stuff* [impl arg1 arg2]
 ...)

(defstate ^:private impl ...)

(defn do-stuff [arg1 arg2]
  (do-stuff* @impl arg1 arg2))
This way, the actual implementation function receives the state directly, which is easier to test. But the public API of this namespace, does not see which states are used at all. It could be none, it could be many; the caller does not care and does not have to pass any states explicitly (i.e. it is private).

onetom 2017-07-11T08:55:39.049190Z

🙂 thats funny (that u havent used with-session at the end)

arnout 2017-07-11T08:59:47.146594Z

^^

onetom 2017-07-11T09:01:48.197904Z

okay, so according to ur example, i would say u did forego on the good practice of passing the state along as arguments to functions 🙂

onetom 2017-07-11T09:02:12.207388Z

and also you did NOT forego at the same time 🙂

arnout 2017-07-11T09:03:17.233338Z

Would it help you if this is bound in the :stop expression?

arnout 2017-07-11T09:03:23.235774Z

Yeah, true

arnout 2017-07-11T09:04:08.252960Z

Maybe I should update the documentation then, if it is not clear what I mean. I hope above example clears it up a bit.

arnout 2017-07-11T09:04:41.265804Z

And this being bound to the start value.

onetom 2017-07-11T09:05:46.290468Z

so then your tests would look like:

(ns app.stuff-test
  (:require [app.stuff :as stuff]))

(def do-stuff* #'stuff/do-stuff*)

(deftest stuff-test
  (let [mock-state (...)]
    (is (do-stuff* mock-state 1 2))))

arnout 2017-07-11T09:06:43.312562Z

yep

onetom 2017-07-11T09:07:00.319130Z

yeah, this sounds quite good, however, it would i need to use it as just this or @this?

arnout 2017-07-11T09:08:44.358321Z

Could be just this, there is no benefit to dereferencing there in my opinion.

onetom 2017-07-11T09:11:54.430928Z

so how would you explain it then? > if you are using the anonymous state constructor macro called state the stop expression will implicitly have access to the value of the anonymous state with the this symbol

onetom 2017-07-11T09:14:06.480687Z

(defstate some-state
  :start 123
  :stop (println "Stopping" @some-state))
as an anonymous state, some-state would be defined as:
(state some-state
  :start 123
  :stop (println "Stopping" this))

onetom 2017-07-11T09:14:29.489297Z

do i understand it well?

onetom 2017-07-11T09:16:31.536404Z

Source of the screenshot: https://youtu.be/13cmHf_kt-Q?t=985

onetom 2017-07-11T09:19:46.608873Z

If you are promoting opaqueness from the public API point of view, but suggest testing via a private API which receives the states it operates on explicitly, then it means the test code will be coupled to the implementation details

arnout 2017-07-11T09:32:51.911890Z

The this would also be bound in the defstate stop expression, but maybe it won't be used.

onetom 2017-07-11T09:39:58.075782Z

> ...also be bound... why also? where else this is bound?

arnout 2017-07-11T09:58:03.477084Z

I mean, not just in anonymous states

onetom 2017-07-11T09:58:53.495891Z

ah, i see. thats good because it means one can reuse stop functions

arnout 2017-07-11T10:17:13.891715Z

@onetom Alright, released a new snapshot, with the this bind in

❤️ 1
tolitius 2017-07-11T14:15:22.796065Z

@onetom: Customers should not be a coupling object inside a component at all. It is better to have two components: db and email. In which case, they can be stubbed for testing easily with mount/start-with. Protocols and Records in this case are "very" optional, but since Component is all about it and your question is specifically about this piece of code:

tolitius 2017-07-11T14:15:36.805844Z

(defprotocol EmailSender
  (send [this address message]))

(defprotocol Database
  (query [this q]))

tolitius 2017-07-11T14:15:44.810620Z

(let [customers {:db (reify Database (query [_ q] (println "querying DB with" q)))  
                 :email (reify EmailSender (send [_ a m] (println "sending email to" a "body:" m)))}]
  (mount/start-with {#'app/customers customers})

tolitius 2017-07-11T14:15:51.815086Z

if we agree not to have a coupling Customers object, and just have db and email instead, we can simply do:

tolitius 2017-07-11T14:15:55.817759Z

(mount/start-with {#'app/db #(println "querying DB with" %)                   ;; or whatever the DB state is
                   #'app/email #(println "sending email to" %1 "body:" %2)})

tolitius 2017-07-11T14:16:04.822925Z

no ceremony.

tolitius 2017-07-11T14:16:22.834684Z

more examples here: https://www.dotkam.com/2016/01/17/swapping-alternate-implementations-with-mount/