Fl4m3Ph03n1x

Fl4m3Ph03n1x

Witchcraft to get something similar to the IO Monad (in Haskell, Scala, etc) but in Elixir?

Background

I have recently been delving into more functional code. My objective right now is to get something similar to the IO Monad (in Haskell, Scala, etc) but in Elixir.

To this extent, I understand Witchcraft should be in theory capable of doing it.

Doubts

However, after reading through their documentation I have some questions:

  • I was not able to find a clear-cut example of the IO Monad. I have the idea what I have to create Monads myself using one of the sub-libraries, but I am not 100% sure of this.
  • I am not sure if Dialzyer plays nice with Witchcraft and if it can detect issues if my code is incorrect (like Scala compiler does).

Could someone help me answer these questions?

Most Liked

lpil

lpil

Creator of Gleam

It’s more the other way around. The static type system makes the IO monad and those checks possible.

Elixir doesn’t yet have a robust static type system so you cannot statically prove properties of your program. There’s work on adding types and other forms of static analysis to Elixir but today you’ll need to look at other BEAM languages such as Gleam or Purerl.

It looks like Witchcraft focuses on the polymorphism aspect of type classes rather than the static verification.

The IO monad only tracks side effects and ordering, so it’s not enough to get that property. You need the more general tool of a robust static type system.

For specifically side effects I think algebraic effects would fit well with Elixir as they are easier to use, have less runtime cost, and map well only existing Elixir code. They don’t do ordering but in a strictly evaluated language that is unimportant.

Macros make implementing a type system more challenging but it’s not a show-stopper, it is still possible.

If you are happy with the level of analysis that Dialyzer offers you then you can implement an IO monad in Elixir without too much pain!

defmodule IOMonad do
  @opaque io(a) :: %__MODULE__{__effect__: (-> a)}
  defstruct :__effect__

  @spec pure((-> a)) :: io(a)
  def pure(effect) do
    %__MODULE__{__effect__: effect}
  end

  @spec map(io(a), (a -> b)) :: io(b)
  def map(io, transform) do
    pure(fn -> transform.(io.__effect__.()))
  end

  @spec bind(io(a), (a -> io(b))) :: io(b)
  def bind(io, transform) do
    pure(fn ->
      transform.(io.__effect__.()).__effect__.()
    end)
  end
end

The same rules apply as in Haskell:

  1. You must wrap all side effecting code in the IO monad rather than performing it immediately.
  2. You must never access __effect__ anywhere in your code, you must always use map and bind.

This unfortunately will be very challenging in Elixir. Unlike in Haskell there is little to verify you are using it correctly, and you’ll have to fork or wrap any libraries you use that have side effects, including the standard library.

Algebraic effects could be used with existing Elixir code without modification but require a more powerful static analysis tool than exists for Elixir today.

edit

As a bonus here’s the same thing in Gleam. The nice thing about this is that the compiler will check that you use it correctly!

pub opaque type Io(a) {
  Io(effect: fn() -> a)
}

pub fn pure(effect) {
  Io(effect)
}

pub fn map(io: Io(a), transform: fn(a) -> b) -> Io(b) {
  Io(fn() { transform(io.effect()) })
}

pub fn bind(io: Io(a), transform: fn(a) -> Io(b)) -> Io(b) {
  Io(fn() { transform(io.effect()).effect() })
}
msimonborg

msimonborg

:eyes: but I thought…

lpil

lpil

Creator of Gleam

Gradient/Gradualizer would do much better! I’m not sure how complete it is but I’m quite excited for it. Looks great.

It’s more of a type checker or compiler feature than a library. The nice thing about it is that for just inferring and restricting effects you don’t need to change the code at all.

Here’s some normal Elixir code

defmodule Main do
  def main do
    text = IO.gets("What's your name?")
    IO.puts("Hello, " <> name)
  end
end

Here it is with an IO monad

defmodule Main do
  def main do
    IOMonad.pure(fn -> IO.gets("What's your name?") end)
    |> IOMonad.map(fn name -> IO.puts("Hello " <> name) end)
    |> IOMonad.unsafe_perform_io()
  end
end

And here it is with algebraic effects.

defmodule Main do
  def main do
    text = IO.gets("What's your name?")
    IO.puts("Hello, " <> name)
  end
end

There’s no changes from the normal code to use IO with this system! All you need to do is annotate functions with any effects they emit.

defmodule MyApp do
  @effects [:console]
  def print_string(string) do
    IO.puts(string)
  end

  @effects [:send_message, :receive_message]
  def run do
    send(self(), "Hello")
    receive do
      x -> x
    end
  end
end

Any functions that call these functions will automatically be detected as also having these effects.

They will be approximately the same. A gradual type system with full annotations should be as sound as Gleam’s type system (with or without annotations) though it comes down to the details of Gradualizer specifically.

Thank you for your support :purple_heart:

The Gleam discord server is often a good place to chat about Gleam or to ask questions. It is quite active these days

Fl4m3Ph03n1x

Fl4m3Ph03n1x

So it would seem my initial observation was somewhat correct. Thanks for the input!

I will politely disagree :smiley:

lpil

lpil

Creator of Gleam

What would you want it to do? In Haskell the IO monad largely has two uses:

  • To express dependencies between different side effecting function calls so that the correct ordering of side effects is maintained in a lazy environment.
  • To enable static tracking of side effects.

Elixir isn’t lazy and doesn’t have Haskell’s static analysis, so what would you want to do with an IO monad?

Unfortunately it does not detect issues statically as Haskell does.

Where Next?

Popular in Questions Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement