View Source WarmFuzzyThing.Either (WarmFuzzyThing v0.1.0)

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):

example

Example

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.

example-1

Example

iex> {:ok, "elixir"}
...> |> WarmFuzzyThing.Either.fmap(fn v -> v <> " with monads" 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)
18

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

example-2

Example

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

Link to this section Summary

Functions

Operator for handling WarmFuzzyThing.Either.fold/3.

Apply a function over the 'right' portion (the "success" value) of an Either monad. The result of the function will be a brand new Either monad. This means that the value inside the monad can change from 'left' to 'right' and vice versa.

Apply a function over the 'right' portion (the "success" value) of an Either monad. The result of the function will be the new 'right' portion of the Either monad.

Depending of the value inside the Either monad, fold will either (no pun intended)

Checks whether a Either monad is 'left'

Similar to how Either.fmap/2 works, but it maps over the Left value of the Either monad.

Call a 'void' type of function if the Either monad is a 'left'. WarmFuzzyThing.Either.on_left/2 returns the Either as is. No changes are applied.

Call a 'void' type of function if the Either monad is a 'right'. WarmFuzzyThing.Either.on_right/2 returns the Either as is. No changes are applied.

Checks whether a Either monad is 'right'

Cycles through a sequence of EIthers, if it reaches a 'left' Either it short circuits and returns it.

Operator for handling WarmFuzzyThing.Either.map/2.

Operator for handling WarmFuzzyThing.Either.bind/2.

Link to this section Types

@type t(reason, value) :: {:error, reason} | {:ok, value}

Link to this section Functions

@spec t(reason, value) <~> ({default :: new_value, function} | function) :: new_value
when reason: any(),
     value: any(),
     function: (value -> new_value),
     new_value: any()

Operator for handling WarmFuzzyThing.Either.fold/3.

example

Example

iex> import WarmFuzzyThing.Either, only: [<~>: 2]
iex> {:ok, 1} <~> fn v -> v + 1 end
2
iex> {:ok, "hello"} <~> fn _v -> "world" end
"world"
iex> {:ok, "hello"} <~> &String.length/1
5
iex> {:error, :not_a_number} <~> fn v -> v + 1 end
nil
iex> {:error, :not_a_number} <~> {:not_found, fn v -> v + 1 end}
:not_found
@spec bind(t(reason, value), function) :: new_either
when reason: any(),
     value: any(),
     function: (value -> new_either),
     new_either: t(new_reason, new_value),
     new_value: any(),
     new_reason: any()

Apply a function over the 'right' portion (the "success" value) of an Either monad. The result of the function will be a brand new Either monad. This means that the value inside the monad can change from 'left' to 'right' and vice versa.

If an Either monad with a 'left' value is passed, the function is not invoked, WarmFuzzyThing.Either.bind/2 just return the 'left' value.

example

Example

iex> WarmFuzzyThing.Either.bind({:ok, 1}, fn v -> {:ok, v + 1} end)
{:ok, 2}
iex> WarmFuzzyThing.Either.bind({:ok, "hello"}, fn _v -> {:ok, "world"} end)
{:ok, "world"}
iex> WarmFuzzyThing.Either.bind({:ok, "hello"}, &({:ok, String.length(&1)}))
{:ok, 5}
iex> WarmFuzzyThing.Either.bind({:ok, "hello"}, fn _v -> {:error, "something went wrong"} end)
{:error, "something went wrong"}
iex> WarmFuzzyThing.Either.fmap({:error, :not_a_number}, fn v -> {:ok, v + 1} end)
{:error, :not_a_number}
@spec fmap(t(reason, value), function) :: t(reason, new_value)
when reason: any(),
     value: any(),
     function: (value -> new_value),
     new_value: any()

Apply a function over the 'right' portion (the "success" value) of an Either monad. The result of the function will be the new 'right' portion of the Either monad.

If an Either monad with a 'left' value is passed, the function is not invoked, WarmFuzzyThing.Either.map/2 just return the 'left' value.

example

Example

iex> WarmFuzzyThing.Either.fmap({:ok, 1}, fn v -> v + 1 end)
{:ok, 2}
iex> WarmFuzzyThing.Either.fmap({:ok, "hello"}, fn _v -> "world" end)
{:ok, "world"}
iex> WarmFuzzyThing.Either.fmap({:ok, "hello"}, &String.length/1)
{:ok, 5}
iex> WarmFuzzyThing.Either.fmap({:error, :not_a_number}, fn v -> v + 1 end)
{:error, :not_a_number}
Link to this function

fold(input, default \\ nil, function)

View Source
@spec fold(t(reason, value), default :: new_value, function) :: new_value
when reason: any(),
     value: any(),
     function: (value -> new_value),
     new_value: any()

Depending of the value inside the Either monad, fold will either (no pun intended):

  • Apply the given function over the 'right' value and return the value itself;
  • Return the 'default' value given to the function. If no 'default' value is provided, nil is returned;

example

Example

iex> WarmFuzzyThing.Either.fold({:ok, 1}, fn v -> v + 1 end)
2
iex> WarmFuzzyThing.Either.fold({:ok, "hello"}, fn _v -> "world" end)
"world"
iex> WarmFuzzyThing.Either.fold({:ok, "hello"}, &String.length/1)
5
iex> WarmFuzzyThing.Either.fold({:error, :not_a_number}, fn v -> v + 1 end)
nil
iex> WarmFuzzyThing.Either.fold({:error, :not_a_number}, :not_found, fn v -> v + 1 end)
:not_found

Checks whether a Either monad is 'left'

example

Example

iex> import WarmFuzzyThing.Either iex> left?({:error, :empty}) true iex> left?({:ok, 1}) false

Link to this function

map_left(either, function)

View Source
@spec map_left(t(reason, value), (reason -> new_reason)) :: t(new_reason, value)
when reason: any(), value: any(), new_reason: any()

Similar to how Either.fmap/2 works, but it maps over the Left value of the Either monad.

Useful when you want to change the structure of the reason.

example

Example

iex> WarmFuzzyThing.Either.map_left({:ok, 1}, fn reason -> {:operation, reason} end)
{:ok, 1}
iex> WarmFuzzyThing.Either.map_left({:error, :not_found}, fn reason -> {:user, reason} end)
{:error, {:user, :not_found}}
Link to this function

on_left(either, function)

View Source
@spec on_left(t(reason, value), function) :: t(reason, value)
when reason: any(), value: any(), function: (reason -> :ok)

Call a 'void' type of function if the Either monad is a 'left'. WarmFuzzyThing.Either.on_left/2 returns the Either as is. No changes are applied.

If the Either has a 'right' value inside, the function is not invoked.

example

Example

iex> WarmFuzzyThing.Either.on_left({:ok, 1}, &IO.inspect/1)
{:ok, 1}
iex> WarmFuzzyThing.Either.on_left({:error, :not_a_number}, &IO.inspect/1)
:not_a_number
{:error, :not_a_number}
Link to this function

on_right(either, function)

View Source
@spec on_right(t(reason, value), function) :: t(reason, value)
when reason: any(), value: any(), function: (value -> :ok)

Call a 'void' type of function if the Either monad is a 'right'. WarmFuzzyThing.Either.on_right/2 returns the Either as is. No changes are applied.

If the Either has a 'left' value inside, the function is not invoked.

example

Example

iex> WarmFuzzyThing.Either.on_right({:ok, 1}, &IO.inspect/1)
1
{:ok, 1}
iex> WarmFuzzyThing.Either.on_right({:error, :not_a_number}, &IO.inspect/1)
{:error, :not_a_number}

Checks whether a Either monad is 'right'

example

Example

iex> import WarmFuzzyThing.Either iex> right?({:ok, 1}) true iex> right?({:error, :empty}) false

@spec sequence([t(reason, value)]) :: t(reason, [value])
when reason: any(), value: any()

Cycles through a sequence of EIthers, if it reaches a 'left' Either it short circuits and returns it.

Otherwise returns a Either with a list of all values.

example

Example

iex> WarmFuzzyThing.Either.sequence([{:ok, 1}, {:ok, 2}])
{:ok, [1, 2]}
iex> WarmFuzzyThing.Either.sequence([])
{:ok, []}
iex> WarmFuzzyThing.Either.sequence([{:ok, 1}, {:error, :not_found}, {:ok, 2}])
{:error, :not_found}
iex> WarmFuzzyThing.Either.sequence([{:error, :not_found}, {:error, :empty}])
{:error, :not_found}
@spec t(reason, value) ~> function :: t(reason, new_value)
when reason: any(),
     value: any(),
     function: (value -> new_value),
     new_value: any()

Operator for handling WarmFuzzyThing.Either.map/2.

example

Example

iex> import WarmFuzzyThing.Either, only: [~>: 2]
iex> {:ok, 1} ~> fn v -> v + 1 end
{:ok, 2}
iex> {:ok, "hello"} ~> fn _v -> "world" end
{:ok, "world"}
iex> {:ok, "hello"} ~> &String.length/1
{:ok, 5}
iex> {:error, :not_a_number} ~> fn v -> v + 1 end
{:error, :not_a_number}
@spec t(reason, value) ~>> function :: t(new_reason, new_value)
when reason: any(),
     value: any(),
     function: (value -> new_either),
     new_either: t(new_reason, new_value),
     new_value: any(),
     new_reason: any()

Operator for handling WarmFuzzyThing.Either.bind/2.

example

Example

iex> import WarmFuzzyThing.Either, only: [~>>: 2]
iex> {:ok, 1} ~>> fn v -> {:ok, v + 1} end
{:ok, 2}
iex> {:ok, "hello"} ~>> fn _v -> {:ok, "world"} end
{:ok, "world"}
iex> {:ok, "hello"} ~>> &({:ok, String.length(&1)})
{:ok, 5}
iex> {:ok, "hello"} ~>> fn _v -> {:error, "something went wrong"} end
{:error, "something went wrong"}
iex> {:error, :not_a_number} ~>> fn v -> {:ok, v + 1} end
{:error, :not_a_number}