Algebraic Effects(-ish Things) in Clojure

I started a blog recently, and after doing some research for some projects and articles I’m putting together I ended writing a few words on what Algebraic Effects are and how we can try and understand them in the context of Clojure today!

Algebraic Effects(-ish Things) in Clojure

Comments and feedback welcome :slight_smile:

1 Like

Hello!

Thanks for the post! I liked it. Hope you keep 'em coming. I have a sweet spot for static typing myself, but Clojure is just so darn nice. I’ve got some questions, if you don’t mind.

  • If you’ve tried it, do you have a comment on Elm’s system for effects and co-effects (subscriptions)? I understand that Elm’s author also has a background from Ocaml.
  • Do you find Duct Boundaries an acceptable solution for effects?

@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 :slight_smile: 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.” :joy:

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.

I think what you say about indirection of implementation and indirection of location makes sense. I tried to formulate it in my own words:

Indirection of implementation – possible to solve by dependency injection; being able to write a service without hard-coding it to what it requires.

Indirection of location – decouple the event from the mechanism of handling the event, so you can refactor and change on the side that receives the events without handling all call sites.

If you end up writing about reactive programming / dataflow programming, I hope you’ll drop off another message here!

It seems to me that algebraic effects only make sense within the context of static types.

The whole area of research is, could we keep track statically of what is normally achieved dynamically. Such that you would know at compile time if there is a mismatch in a function executing something defined elsewhere, and what the function expects.

Basically, I see Algebraic Effects as a specific implementation strategy of Aspect Oriented Programming. Which I think is difficult to implement in a strict FP type system, without breaking some of the guarantees of the type system.

Now, could you argue static verification could make AOT a lot more practical and widespread, maybe. But currently, you can do AOT in some ways in Clojure and other languages. Dynamic vars are a pretty easy way to do it. Event frameworks sometimes enables that as well. Or you can go real hardcore and just alter-var-root using something like robert-hooke.

Overall, it reminds me of Common Lisp’s condition system, but with static types added. A good Clojure condition system like library I recommend you try is Special.

3 Likes

This is awesome! I’m definitely coming from having been reading about multi-core OCaml; I had not heard of CL’s condition system before, but sounds exactly what I had thought of w.r.t. Clojure!

I’m going to spend all day playing with Special now :smiley:

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.