[Question / Help] Can anybody explain how to use Free monad?
I've been search for a good tutorial on understanding this Monad but I'm unable to grasp the concept. I understand how IO monads work. Can anybody explain how to use Free monad to perform IO operation or DB writes with Ecto ?
Simply explaining with IO.puts() is enough.
Thanks,
Hari Roshan
@expede can you help?
Hey @hariroshan 👋
I've been search for a good tutorial on understanding this Monad but I'm unable to grasp the concept.
http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html
Can anybody explain how to use Free monad to perform IO operation or DB writes with Ecto ?
I don’t know if I’d start with the free monad when trying to understand the concept of monads broadly. IO is also a bit of a weird case that will not build your intuition.
Much ink has been spilled on the topic of “what is a monad / monad tutorial”, but they generally don’t work because there’s an abstraction that’s not obvious until you work with them a bit. The good news is that you can build this intuition pretty quickly, but you need to write some code.
I’d work though writing instances for Identity, List, Either, and Writer from scratch. This means implementing the following type classes (fancy super protocols provided by Witchcraft) for each of them:
- Functor
- Apply
- Applicative
- Chain
- Monad
The TL;DR is that in the same way that map lets you apply a function to every element in some container without changing its structure (that’s Functor.map, not Enum.map), “monad” is an abstraction over things that have some pure “effect” automatically book-kept by the instance. If you’re familiar with JS, Promises are the kind of “effect” that I’m talking about. It turns out that when you look at the structure of lots of computations, there’s similarities, even though the surface details are different (which is just “abstraction”). This means that you can then write code once, and then run it in any monad and have it take care of different things for you. For example:
- Writer — log messages to a store that is invisibly kept next to the data it references. You can send the data and log across the wire, persist them to disk and resume later, just debug the path that got you to some state, and so on.
- Either — totally pure version of
with(automated error handling) - State — a totally pure equivalent to an
Agent(and because it’s restricted as pure you can have an impure implementation that’s say, based on Agent or even RAFT without needing to change your code just, the specialized monad that it’s running in) - List — the effect of running the same functions (plural!) over multiple inputs (also plural). Sometimes call the indeterminacy monad
- Many, many more
Not sure if this video is helpful for your case, but can’t hurt: https://www.youtube.com/watch?v=psdG5iV57q0
Free Monad
If you’ve ever written an interpreter or a toy languages, the free monad lets you do that in a very clean way. Because DSLs can express literally any domain, it’s a super powerful tool! You also get the added benefit of being able to debug the AST before (or during) running it, which makes debugging incredibly simple. It’s also restricted to your DSL, so it can’t do anything unexpected like sending an unexpected email when your program is for doing DB actions.
The free monad takes the “essence” of a monad, and encodes that. You can then embed a functor that describes a DSL that you’re trying to express. This builds up an AST for your DSL (the same general idea as the Elixir AST, but specific to your domain). You then pass this structure off to an interpreter, and as you tear it down, re-run branches (for loops), and adjust the tree of the runtime, you perform effects in the context of the interpreter (e.g. just regular Elixir). The interpreter is a very simple function that has a case for each command in your DSL, and does something with it. This gives you a very very clean, decomposed structure (though at the cost of performance unless you’re in Haskell GHC >= 8.10 with some features enabled).
There’s a lot of excitement around free monads right now, because they’re so flexible, are equally expressive as any other monad, are super clean / maintainable. But in Elixir they do with runtime cost (that interpreting+data structure). You can make something similar in a macro (free applicative) and push the performance cost to compile time, but you loose the ability to react/change based on the result of a step — a list instead of a tree.
I hope that’s helpful! In my experience teaching a bunch of people these concepts, there’s really no substitute for just rolling up your sleeves and trying out. There’s really nothing comparable that you can build a solid intuition around other than perhaps saying “it’s like JS promises applied to different domains.” It’s a new, different, super powerful way of thinking, in the same way that anonymous functions, pure functions, and actors are super powerful tools. And just like actors, once you understand a monad, they seem obvious — but getting over that first hill is the hardest bit.
Thank you for your descriptive reply. @expede 👏 🎉 I will practice writing some of the type classes and look through the documentation and code implementation of Algae. I have understanding of map/2, apply/2 and bind/2 or chain/2. I know how to Either and Maybe well enough to do Railway Oriented Programming.
I learned most of the concepts from Professor frisby's mostly adequate guide to functional programming book. In this he implemented IO monad approximately as
IO.of( ( unit -> any ) ) :: {:io, ( unit -> any )}
map/2 and bind/2 are implemented as function compositions. Finally, he uses a run/1 function to execute the IOs. Thus making the caller to decide whether to execute side effects or not. Is this how we make impure functions to pure functions by making them return an option IO ?
For example
add(integer, integer) :: {integer, Maybe.t(IO.t())}
Also I tried executing similar things with Free monad in IEx. It does take a function and composes them while mapping.
For example I have,
@spec initialize ::
Free.Roll.t(
({ipAddress, port_number()} ->
Either.t(
:gen_tcp.reason() | String.t(),
port
)
)
)
def initialize do
Free.free(fn {ip, port_number} ->
@infrastructure.testConnection(ip, port_number)
|> bind(fn connected_port ->
Port.close(connected_port)
@infrastructure.start_connection(getPortJSPath())
end)
|> map(fn port ->
port
|> @infrastructure.sendValue(0)
|> @infrastructure.sendValue(
{:init, %{"ip" => ip |> to_charlist, "port" => port_number}}
)
port
end)
end)
end
Is this how we use Free monad? Or should we implement our own using Typeclasses? I hope I'm not bothering Thanks, Hari Roshan 😄