Closed tolitius closed 7 years ago
not to solve two things at once and not to overload start-with
, would make sense to first start with just functions that could simply be composed:
(-> (only 1 2 3 4)
(with-args {})
(except 2 1)
(swap-states {4 #'foo.bar/5})
(swap {2 42 1 34})
(mount/start))
this way the current start-with
won't change, neither would (mount/start)
since it optionally takes states since day one.
Hi @tolitius, me again. I have given it some more thought as well, and I could not resist to write to you. Keep in mind that I love mount.
Here's the thing. I really urge you to reconsider going for a data-driven approach for the composability you want to add (as you show in the opening post of this issue). I feel the gains are superseding the possible negatives. A data-driven approach decomplects, something the users might like. It decomplects forming the start/stop options and performing the start/stop options. Also functions like except
now uses the set of available states (find-all-state
) when except
is called, and that set may have changed already when start
or stop
is called. You say that users will thread start
or stop
immediatly after, and that may be true. I do that myself as well. But with a data-driven approach, it does give the possibility of separating creating the options and performing start/stop, and is also less suprising to the user.
But don't do it for your users per se, the gain is also for yourself, as the library developer. It makes it easier to change the internals of the composable functions (because hey, you are only transforming data you fully control) and it makes it easier to add more options later on. Take for instance the parallel
option that mount-lite offers. I know you are not interested in doing that, but let's just take it as an example. In your current design, the parallel
function would take a set of states (or call find-all-state
), and return the same set of states. This is because parallel
does not have to do anything with the set of states; it just specifies the number of threads you want to use. How are you going to make sure such an option reaches start
or stop
? With with-meta
on the set of states? That would be kind of dangerous, as all the other transformations must make sure that meta data is kept. Or add another function called start-parallel
? I think you'd rather not as I know you want to keep the API minimal. With a data-driven approach, adding such an option is no problem. This also benefits the API stability which, again, I think the users will appreciate.
Alright, so much for the background of the main point I think I want to make. What I want to say is, I believe that going for the data-driven approach now, instead of going for two APIs (one with functions that compose and call start
, and one with data that calls start-with
), is beneficial because it will provide a single API with less suprises, gives you more flexibility and may prevent an API that "needs" changes in the future when users ask for some feature. Many gains, with the loss that the semantics of start-with
has to be changed.
I hope you don't mind me rambling on over this. If you do, please say so as well.
@aroemers
I hope you don't mind me rambling on over this.
I love feedback. I never mind it.
I believe that going for the data-driven approach now, instead of going for two APIs (one with functions that compose and call start, and one with data that calls start-with), is beneficial.
I agree with you in theory :) And I have plans to add data driven start config which maybe useful in places like lein
profiles, but I also like having these building functions. It comes down to declaring
vs. programming
. I don't want to take the programming
out of building states. Having this functions help tearing application states apart and composing them back in different order, shape or form. Data allows do the same thing, but these functions are a lot more primitive, hence to understand and reason about them is a lot simpler than about a config DSL
.
Your main objection is that they do stuff, rather than declare what to do. And in REPL I mostly want to do stuff. mount has two different goals: manage application state and help development with stateful resources. I like certain side effects in development that allow me to "touch" the code. I like breaking things, and making new ones.
"Starting mount" is not something you, as an application developer, base your APIs on. mount, once started, is not really used that much in production, unless you need to hotswap / partially start stop things via nREPL, etc. which is quite rare. That's why I'd like to have as much flexibility as I want in development with mainly designing states so the focus of production applications in just on (mount/start)
.
Hi @tolitius, sorry for not getting back to you sooner. I understand your point of view, thank you for explaining. I think mount users are happy with the composability you have given them. As far ar I am concerned, this "issue" can be closed.
While it's good to have separate APIs to swap / filter states needed. It is limiting if they don't compose.
We started discussing it here with @aroemers, and this was one of the primary reasons for a mount-lite spinoff.
I really want to preserve the
vararg
ness of(mount/start)
/(mount/stop)
since it is really simple and useful in REPL, hence I don't want to overload them. But I feel(mount/start-with)
can be a great candidate for composing the mount configuration. e.g.This is the current approach that we discussed with @wkf on
#mount
slack channel, and it does break the existing(mount/start-with)
, but I feel the timing and the nature of its use still permits it.