View Source WarmFuzzyThing

hex.pm version Hex Docs Total Download License

Simple way of working with Maybe and Either monads in Elixir. Both monads are setup in a way that allows for easy plug in into an already existing Elixir system since they don't rely on custom structures. Rather they rely on the already established ways of handling data in Elixir.

why-warm-fuzzy-thing

Why Warm fuzzy thing?

It's just a sweeter way of saying Monad.

using

Using

Both Maybe and Either monads have been setup to use already imposed structures by Elixir/Erlang standards.

The difference is that the Either monad is setup for holding errors while the Maybe monad only cares if there is a value or not.

In Elixir usually to chain transformations and make sure that each step is correct we might use with

with {:ok, value1} <- transform_1(input),
     {:ok, value2} <- transform_2(value1),
     {:ok, value3} <- transform_3(value2) do
  # Handle success case
else 
  {:error, reason} -> # Handle error case
end

Using WarmFuzzyThing.Either this could look something like this

input
|> Either.pure()
|> Either.bind(&transform_1/1)
|> Either.bind(&transform_2/1)
|> Either.bind(&transform_3/1)
|> case do
  {:ok, value3} -> # Handle success case
  {:error, reason} -> # Handle error case
end
iex> alias WarmFuzzyThing.Either
WarmFuzzyThing.Either
iex> {:ok, 1}
|> Either.map(fn v -> v + 1 end)
|> Either.map(fn v -> v + 1 end)
|> Either.map(fn v -> v + 1 end)
|> Either.fold(&WarmFuzzyThing.id/1)
4

installation

Installation

Add :warm_fuzzy_thing as a dependency to your project's mix.exs:

defp deps do
  [
    {:warm_fuzzy_thing, "~> 0.1.0"}
  ]
end

maybe

Maybe

The Maybe monad is a union between two general notions: Just and Nothing (the naming is inherited from Haskell and it depicts the idea: "Maybe I have a value, maybe I don't."). This means that a Maybe monad can either be a Just or it can be a Nothing (think of it as a simple union between two types). A Just is represented by the well known success type tuple {:ok, value} when value: any(), where as a Nothing is represented by nil, since it's the closest Elixir gets to representing "nothing".

Maybe exports a set of function for ease of chaining functions (transformations):

iex> WarmFuzzyThing.Maybe.fmap({:ok, 1}, fn v -> v + 1 end)
{:ok, 2}
iex> WarmFuzzyThing.Maybe.fmap(nil, fn v -> v + 1 end)
nil
iex> WarmFuzzyThing.Maybe.bind({:ok, 1}, fn v -> {:ok, v + 1} end)
{:ok, 2}
iex> WarmFuzzyThing.Maybe.bind(nil, fn v -> {:ok, v + 1} end)
nil
iex> WarmFuzzyThing.Maybe.bind({:ok, 1}, fn v -> nil end)
nil
iex> WarmFuzzyThing.Maybe.fold({:ok, 1}, &WarmFuzzyThing.id/1)
1
iex> WarmFuzzyThing.Maybe.fold(nil, &WarmFuzzyThing.id/1)
nil
iex> WarmFuzzyThing.Maybe.fold(nil, :empty, &WarmFuzzyThing.id/1)
:empty

This set of function are setup in a way to allow for easy pipelining.

example

Example

iex> {:ok, "hello"}
...> |> WarmFuzzyThing.Maybe.fmap(fn v -> v <> " world" end)
...> |> WarmFuzzyThing.Maybe.fmap(&String.length/1)
...> |> WarmFuzzyThing.Maybe.bind(fn
...>    v when v <= 20 -> {:ok, v}
...>    _ -> nil
...>  end)
...> |> WarmFuzzyThing.Maybe.fold(&WarmFuzzyThing.id/1)
11

Maybe exports a set of operators for handling the chaining functions:

iex> import WarmFuzzyThing.Maybe, only: [~>: 2, ~>>: 2, <~>: 2]
iex> {:ok, "elixir"} ~> &String.length/1
{:ok, 6}
iex> nil ~> &String.length/1
nil
iex> {:ok, "elixir"} ~>> &({:ok, String.length(&1)})
{:ok, 6}
iex> {:ok, ""} ~>> fn "" -> nil; v -> {:ok, String.length(v)} end
nil
iex> {:ok, "elixir"} <~> &String.length/1
6
iex> nil ~>> fn v -> {:ok, v + 1} end
nil
iex> nil <~> fn v -> v + 1 end
nil
iex> nil <~> {:not_found, fn v -> v + 1 end}
:not_found
iex> {:ok, "elixir"}
...> ~> fn v -> v <> " with monads" end
...> ~> fn v -> v <> " is awesome" end
...> ~>> fn v when v < 20 -> nil; v -> {:ok, String.length(v)} end
...> <~> {0, &WarmFuzzyThing.id/1}
29

either

Either

The Either monad is a union between two general notions: Left and Right (the naming is inherited from Haskell and it depicts the idea: "Either I have this or I have that."). This means that an Either monad can either be a Left or it can be a Right (think of it as a simple union between two types). A Right is represented by the well known success type tuple {:ok, value} when value: any(), where as a Left is represented by the well known error type tuple {:error, reason} when reason: any(). Generally a Right Either represents a successful transformation/operation where as a Left Either represents an unsuccessful one.

Either exports a set of function for ease of chaining functions (transformations):

iex> WarmFuzzyThing.Either.fmap({:ok, 1}, fn v -> v + 1 end)
{:ok, 2}
iex> WarmFuzzyThing.Either.fmap({:error, :not_found}, fn v -> v + 1 end)
{:error, :not_found}
iex> WarmFuzzyThing.Either.bind({:ok, 1}, fn v -> {:ok, v + 1} end)
{:ok, 2}
iex> WarmFuzzyThing.Either.bind({:error, :not_found}, fn v -> {:ok, v + 1} end)
{:error, :not_found}
iex> WarmFuzzyThing.Either.bind({:ok, 1}, fn v -> {:error, :not_found} end)
{:error, :not_found}
iex> WarmFuzzyThing.Either.fold({:ok, 1}, &WarmFuzzyThing.id/1)
1
iex> WarmFuzzyThing.Either.fold({:error, :not_found}, &WarmFuzzyThing.id/1)
nil
iex> WarmFuzzyThing.Either.fold({:error, :not_found}, :empty, &WarmFuzzyThing.id/1)
:empty

This set of function are setup in a way to allow for easy pipelining.

iex> {:ok, "hello"}
...> |> WarmFuzzyThing.Either.fmap(fn v -> v <> " world" end)
...> |> WarmFuzzyThing.Either.fmap(&String.length/1)
...> |> WarmFuzzyThing.Either.bind(fn
...>    v when v <= 20 -> {:ok, v}
...>    _ -> {:error, :too_big}
...>  end)
...> |> WarmFuzzyThing.Either.fold(&WarmFuzzyThing.id/1)
11

Either exports a set of operators for handling the chaining functions:

iex> import WarmFuzzyThing.Either, only: [~>: 2, ~>>: 2, <~>: 2]
iex> {:ok, "elixir"} ~> &String.length/1
{:ok, 6}
iex> {:error, :not_found} ~> &String.length/1
{:error, :not_found}
iex> {:ok, "elixir"} ~>> &({:ok, String.length(&1)})
{:ok, 6}
iex> {:ok, ""} ~>> fn "" -> {:error, :empty}; v -> {:ok, String.length(v)} end
{:error, :empty}
iex> {:ok, "elixir"} <~> &String.length/1
6
iex> {:error, :not_a_number} ~>> fn v -> {:ok, v + 1} end
{:error, :not_a_number}
iex> {:error, :empty} <~> fn v -> v + 1 end
nil
iex> {:error, :empty} <~> {:not_found, fn v -> v + 1 end}
:not_found
iex> {:ok, "elixir"}
...> ~> fn v -> v <> " with monads" end
...> ~> fn v -> v <> " is awesome" end
...> ~>> fn v when v < 20 -> {:error, :too_short}; v -> {:ok, String.length(v)} end
...> <~> {0, &WarmFuzzyThing.id/1}
29