defmodule Algae.Free do @moduledoc """ A "free" structure that converts functors into monads by embedding them in a special structure with all of the monadic heavy lifting done for you. Similar to trees and lists, but with the ability to add a struct "tag", at each level. Often used for DSLs, interpreters, or building structured data. For a simple introduction to the "free monad + interpreter" pattern, we recommend [Why free monads matter](http://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html). ## Anatomy ### Pure `Pure` simply holds a plain value. %Free.Pure{pure: 42} ### Roll `Roll` resursively containment of more `Free` structures embedded in a another ADT. For example, with `Id`: %Free.Roll{ roll: %Id{ id: %Pure{ pure: 42 } } } """ alias __MODULE__ alias Algae.Free.{Pure, Roll} import Algae use Witchcraft defsum do defdata(Roll :: any()) defdata(Pure :: any() \\ %Witchcraft.Unit{}) end @doc """ Create an `Algae.Free.Pure` wrapping a single, simple value ## Examples iex> new(42) %Algae.Free.Pure{pure: 42} """ @spec new(any()) :: t() def new(value), do: %Pure{pure: value} @doc """ Add another layer to a free structure ## Examples iex> 13 ...> |> new() ...> |> layer(%Algae.Id{}) %Algae.Free.Roll{ roll: %Algae.Id{ id: %Algae.Free.Pure{ pure: 13 } } } """ @spec layer(t(), any()) :: t() def layer(free, mutual), do: %Roll{roll: of(mutual, free)} @doc """ Wrap a functor in a free structure. ## Examples iex> wrap(%Algae.Id{id: 42}) %Algae.Free.Roll{ roll: %Algae.Id{ id: 42 } } """ @spec wrap(Witchcraft.Functor.t()) :: Roll.t() def wrap(functor), do: %Roll{roll: functor} @doc """ Lift a plain functor up into a free monad. ## Examples iex> free(%Algae.Id{id: 42}) %Algae.Free.Roll{ roll: %Algae.Id{ id: %Algae.Free.Pure{ pure: 42 } } } """ @spec free(Witchcraft.Functor.t()) :: t() def free(functor) do functor |> map(&of(%Roll{}, &1)) |> wrap() end end alias Algae.Free alias Algae.Free.{Pure, Roll} alias TypeClass.Property.Generator alias Witchcraft.{Apply, Chain, Functor, Ord, Setoid} import TypeClass use Witchcraft ############# # Generator # ############# defimpl TypeClass.Property.Generator, for: Algae.Free.Pure do def generate(_) do [1, 1.1, "", []] |> Enum.random() |> Generator.generate() |> Pure.new() end end defimpl TypeClass.Property.Generator, for: Algae.Free.Roll do def generate(_) do inner = Algae.Id.new() seed = [1, 1.1, "", []] |> Enum.random() |> Generator.generate() seed |> Free.new() |> Free.layer(inner) |> Free.layer(inner) end end ########## # Setoid # ########## definst Witchcraft.Setoid, for: Algae.Free.Pure do custom_generator(_) do 1 |> Generator.generate() |> Pure.new() end def equivalent?(_, %Roll{}), do: false def equivalent?(%Pure{pure: a}, %Pure{pure: b}), do: Setoid.equivalent?(a, b) end definst Witchcraft.Setoid, for: Algae.Free.Roll do custom_generator(_) do inner = Algae.Id.new() seed = Generator.generate(1) seed |> Free.new() |> Free.layer(inner) |> Free.layer(inner) end def equivalent?(_, %Pure{}), do: false def equivalent?(%Roll{roll: a}, %Roll{roll: b}), do: Setoid.equivalent?(a, b) end ####### # Ord # ####### definst Witchcraft.Ord, for: Algae.Free.Pure do custom_generator(_) do 1 |> TypeClass.Property.Generator.generate() |> Free.new() end def compare(_, %Roll{}), do: :lesser def compare(%Pure{pure: a}, %Pure{pure: b}), do: Ord.compare(a, b) end definst Witchcraft.Ord, for: Algae.Free.Roll do custom_generator(_) do inner = Algae.Id.new() seed = Generator.generate(1) seed |> Free.new() |> Free.layer(inner) |> Free.layer(inner) end def compare(%Roll{}, %Pure{}), do: :greater def compare(%Roll{roll: a}, %Roll{roll: b}), do: Ord.compare(a, b) end ########### # Functor # ########### definst Witchcraft.Functor, for: Algae.Free.Pure do def map(%Pure{pure: data}, fun), do: %Pure{pure: fun.(data)} end definst Witchcraft.Functor, for: Algae.Free.Roll do def map(%Roll{roll: data}, fun) do data |> Functor.map(&Functor.map(&1, fun)) |> Roll.new() end end ######### # Apply # ######### definst Witchcraft.Apply, for: Algae.Free.Pure do def convey(%Pure{pure: data}, %Pure{pure: fun}), do: %Pure{pure: fun.(data)} def convey(pure, %Roll{roll: rolled}) do rolled |> Functor.map(&Apply.convey(pure, &1)) |> Roll.new() end end definst Witchcraft.Apply, for: Algae.Free.Roll do def convey(%Roll{roll: rolled}, %Pure{pure: fun}) do rolled |> Functor.map(&Functor.map(&1, fun)) |> Roll.new() end def convey(roll, %Roll{roll: rolled}) do rolled |> Functor.map(&Apply.convey(roll, &1)) |> Roll.new() end end ############### # Applicative # ############### definst Witchcraft.Applicative, for: Algae.Free.Pure do def of(_, value), do: %Pure{pure: value} end definst Witchcraft.Applicative, for: Algae.Free.Roll do def of(_, value), do: %Pure{pure: value} end ######### # Chain # ######### definst Witchcraft.Chain, for: Algae.Free.Pure do def chain(%Pure{pure: pure}, link), do: link.(pure) end definst Witchcraft.Chain, for: Algae.Free.Roll do def chain(%Roll{roll: rolled}, link) do rolled |> Functor.map(&Chain.chain(&1, link)) |> Roll.new() end end ######### # Monad # ######### definst(Witchcraft.Monad, for: Algae.Free.Pure) definst(Witchcraft.Monad, for: Algae.Free.Roll)