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

Where Next?

Popular in Questions Top

Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&amp;query=perfume&amp;page=2, I would like to get: ...
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
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: &lt;h1&gt;Create Post&lt;/h1&gt; &lt;%= ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
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
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42158 114
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
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
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
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
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36432 110
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

We're in Beta

About us Mission Statement