Closed aroemers closed 8 years ago
:start (fn [n] (+ n i))
looks odd to me, as you're defining an anonymous function, yet the i
is not declared in the lines above, but in bindings.
What happens if you do not provide i
in bindings? Do bindings behave like with-redefs
?
@vizanto If i
is not in the bindings (and also not in the environment of the defstate
), the standard "symbol not found" exception will occur when loading the source code.
What behaviour of with-redefs
are you referring to? They are not thread-local, if that's what you mean (it actually avoids it), and they are also not visible outside of the defstate
's scope.
About the order, you are right. Of course, you could put the :bindings
above the :start
expression. I also thought if this syntax:
(defstate incrementer [i 10]
:start #(+ % i))
Would that improve things?
Above syntax idea is now supported.
(defstate incrementer [i 10]
:start #(+ % i))
When started, a :mount.lite/current-bindings
var meta key is set to the actual binding values, for debugging purposes.
(mount/start)
(-> #'incrementer meta :mount.lite/current-bindings)
;=> [i 10]
(stop)
(mount/start (bindings #'incrementer '[i 20])
(-> #'incrementer meta :mount.lite/current-bindings)
;=> [i 20]
Note that the bindings option now takes a vector, instead of a map.
That is more explicit, yes. The more explicit syntax in the lasts commits seems cleaner to me. :+1:
I meant (with-redefs [#'i 10] ...)
redefines a var named i, which was not declared in the original example. So standard symbol not found error kinda makes sense.
From the README:
Bindings
It is generally best to define the
defstate
s in application namespaces, not in the more general (library) namespaces. This is because the:start
and:stop
expressions are tightly coupled to their environment, including references to other states. This is fine though, as you as the application writer have full control over your states, and resources should be at the periphery of the application anyway.Yet, in the rare situations where you need a looser coupling between the
:start
/:stop
expressions and their environment, mount-lite has a unique feature called bindings. When defining adefstate
, one can optionally supply a:bindings
vector, next to the:start
and:stop
expressions. This vector declares the bindings that can be used by the:start
/:stop
expressions, and their defaults. For example:When the
incrementer
state is started normally, it will become a function that increments the argument by 10. However, one can start theincrementer
with different bindings, like so:As can be seen, the bindings that were used when starting the state are also used when stopping the state.
This bindings feature can be used as a kind of dependency injection or for passing configuration parameters. Yet, at the current time of writing, my opinion is to use this feature sparingly. Substitutions are normally sufficient and using bindings a lot might hint towards a design flaw.
My opinion as of the time of this writing:
For passing in some configuration values (e.g. command line arguments), I think this is a very nice solution: no need for thread-local dynamic vars (which will break in parallel mode) or other fragile and rigid solutions. Bindings in that sense offer an easy, cleanly scoped and semantically clear way of passing values to states, ensuring the same values on stop as when a state was started.
Yet, the number of use cases currently seem very slim. In my opinion it should not be used to pass one state/resource to another state as some form of dependency injection. States can simply refer to those states/resources directly; which is the ease that mount offers. And those referred states can be substituted. Most configuration of states would be by referring to some config state. So, only this initial config state might benefit from the bindings feature.
So, powerful in theory (as it removes the last "limitation" of mount - tight coupling), but not many use cases in practice? and maybe too powerful leading to "wrong" usage of mount? The tight coupling limitation is a good thing, because states should be in fully controlled application namespaces anyway?
Time will tell. But anyone reading this, know that this feature exists (which was only 12 lines of real code anyway), and please share your use case or opinion on the matter.