@teodorlu Thanks for reading! Great questions, I’ll see if I can answer them.
Re: Elm
I have not tried Elm before (I have read a bit about it), but I have used re-frame, which AFAICT is architecturally very similar.
The strategy used for managing side effects in Elm / re-frame is a form of reactive or dataflow programming. This is another topic I’m actively researching and will try and write more on later the reason I added the section at the end of the article about Reagent’s Reaction
was to help myself build towards talking about dataflow programming in terms of algebraic effects.
What I will say now, is that in my experience the tradeoff with using a reactive strategy is that it has many implications on the runtime behavior of your program that may help or hinder you.
E.g. it seems to hit a very nice sweet spot in terms of developer ergonomics for UI programming, but has plenty of gotchas. You may not need or want this behavior for a compiler, or an ETL batch job. Or maybe in some parts, and not others.
Like static types, you end up warping your application to fit within the Elm/re-frame mold. In many cases this is good, but it is quite prescriptive.
Re: Duct Boundaries:
I haven’t used Duct in anger, but from the looks Duct boundaries are just good practices for handling resources that you expect the implementation to change over time. It is the “normal” level of indirection that we can leverage in all of our Clojure projects today.
If you think about it, algebraic effects can fill two kinds of indirection. I’m going to make up some phrases (I hope they make sense):
-
Indirection of implementation. For example, (perform :get-user)
allows us to change the implementation of what :get-user
does
-
Indirection of location. For example, (perform :get-user)
might have a handler at the top of my application to fetch something from the database, but because it doesn’t refer to a particular var or value I can swap that at any level in my app without having to thread that change through each call site in my code.
Protocols provide the former, but not the latter. Dynamic vars and frameworks like component and integrant try to solve the latter, but not the former.
Now, I just watched Stu Halloway’s latest talk where he said, “Anytime I notice two concepts implemented in one place, I don’t always know why I’ll want them to be separate. But almost always I eventually do.”
So maybe in Clojure, keeping these two things separate is good! I haven’t figured out all the intricacies yet, but the ease with which algebraic effects could give me both of these powerful tools entices me.