Witchcraft.Chain (Witchcraft v1.0.4) View Source
Chain function applications on contained data that may have some additional effect
As a diagram:
%Container<data> --- (data -> %Container<updated_data>) ---> %Container<updated_data>
Examples
iex> chain([1, 2, 3], fn x -> [x, x] end)
[1, 1, 2, 2, 3, 3]
alias Algae.Maybe.{Nothing, Just}
%Just{just: 42} >>> fn x -> %Just{just: x + 1} end
#=> %Just{just: 43}
%Just{just: 42}
>>> fn x -> if x > 50, do: %Just{just: x + 1}, else: %Nothing{} end
>>> fn y -> y * 100 end
#=> %Nothing{}
Type Class
An instance of Witchcraft.Chain
must also implement Witchcraft.Apply
,
and define Witchcraft.Chain.chain/2
.
Functor [map/2]
↓
Apply [convey/2]
↓
Chain [chain/2]
Link to this section Summary
Functions
Operator alias for draw/2
Operator alias for chain/2
.
An alias for chain/2
.
do
notation sugar
Sequentially compose actions, piping values through successive function chains.
Compose link functions to create a new link function.
chain/2
but with the arguments flipped.
Join together one nested level of a data structure that contains itself
Compose link functions to create a new link function.
Link to this section Types
Specs
Specs
t() :: any()
Link to this section Functions
Specs
Operator alias for draw/2
Extends the <~
/ <<~
heirarchy with one more level of power / abstraction
Examples
iex> to_monad = fn x -> (fn _ -> x end) end
...> bound = to_monad.(&(&1 + 10)) <<< to_monad.(&(&1 * 10))
...> bound.(10)
20
In Haskell, this is the famous =<<
operator, but Elixir doesn't allow that
infix operator.
Specs
Operator alias for chain/2
.
Extends the ~>
/ ~>>
heirarchy with one more level of power / abstraction
Examples
iex> to_monad = fn x -> (fn _ -> x end) end
...> bound = to_monad.(&(&1 * 10)) >>> to_monad.(&(&1 + 10))
...> bound.(10)
20
In Haskell, this is the famous >>=
operator, but Elixir doesn't allow that
infix operator.
Specs
An alias for chain/2
.
Provided as a convenience for those coming from other languages.
do
notation sugar
Sequences chainable actions. Note that each line must be of the same type.
For a version with return
, please see Witchcraft.Monad.monad/2
Examples
iex> chain do
...> [1]
...> end
[1]
iex> chain do
...> [1, 2, 3]
...> [4, 5, 6]
...> [7, 8, 9]
...> end
[
7, 8, 9,
7, 8, 9,
7, 8, 9,
7, 8, 9,
7, 8, 9,
7, 8, 9,
7, 8, 9,
7, 8, 9,
7, 8, 9
]
iex> chain do
...> a <- [1, 2, 3]
...> b <- [4, 5, 6]
...> [a * b]
...> end
[
4, 5, 6,
8, 10, 12,
12, 15, 18
]
Normal functions are fine within the do
as well, as long as each line
ends up being the same chainable type
iex> import Witchcraft.{Functor, Applicative}
...> chain do
...> map([1, 2, 3], fn x -> x + 1 end)
...> of([], 42)
...> [7, 8, 9] ~> fn x -> x * 10 end
...> end
[
70, 80, 90,
70, 80, 90,
70, 80, 90
]
Or with a custom type
alias Algae.Maybe.{Nothing, Just}
chain do
%Just{just: 4}
%Just{just: 5}
%Just{just: 6}
end
#=> %Just{just: 6}
chain do
%Just{just: 4}
%Nothing{}
%Just{just: 6}
end
#=> %Nothing{}
let
bindings
let
s allow you to hold static or intermediate values inside a
do-block, much like normal assignment
iex> chain do
...> let a = 4
...> [a]
...> end
[4]
iex> chain do
...> a <- [1, 2]
...> b <- [3, 4]
...> let [h | _] = [a * b]
...> [h, h, h]
...> end
[3, 3, 3, 4, 4, 4, 6, 6, 6, 8, 8, 8]
Desugaring
Sequencing
The most basic form
chain do
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
end
is equivalent to
[1, 2, 3]
|> then([4, 5, 6])
|> then([7, 8, 9])
<-
("drawn from")
Drawing values from within a chainable structure is similar feels similar to assignmet, but it is pulling each value separately in a chain link function.
For instance
iex> chain do
...> a <- [1, 2, 3]
...> b <- [4, 5, 6]
...> [a * b]
...> end
[4, 5, 6, 8, 10, 12, 12, 15, 18]
desugars to this
iex> [1, 2, 3] >>> fn a ->
...> [4, 5, 6] >>> fn b ->
...> [a * b]
...> end
...> end
[4, 5, 6, 8, 10, 12, 12, 15, 18]
but is often much cleaner to read in do-notation, as it cleans up all of the nested functions (especially when the chain is very long).
You can also use values recursively:
# iex> chain do
# ...> a <- [1, 2, 3]
# ...> b <- [a, a * 10, a * 100]
# ...> [a + 1, b + 1]
# ...> end
# [
# 2, 2, 2, 11, 2, 101,
# 3, 3, 3, 21, 3, 201,
# 4, 4, 4, 31, 4, 301
# ]
Specs
Sequentially compose actions, piping values through successive function chains.
The applied linking function must be unary and return data in the same type of container as the input. The chain function essentially "unwraps" a contained value, applies a linking function that returns the initial (wrapped) type, and collects them into a flat(ter) structure.
chain/2
is sometimes called "flat map", since it can also
be expressed as data |> map(link_fun) |> flatten()
.
As a diagram:
%Container<data> --- (data -> %Container<updated_data>) ---> %Container<updated_data>
Examples
iex> chain([1, 2, 3], fn x -> [x, x] end)
[1, 1, 2, 2, 3, 3]
iex> [1, 2, 3]
...> |> chain(fn x -> [x, x] end)
...> |> chain(fn y -> [y, 2 * y, 3 * y] end)
[1, 2, 3, 1, 2, 3, 2, 4, 6, 2, 4, 6, 3, 6, 9, 3, 6, 9]
iex> chain([1, 2, 3], fn x ->
...> chain([x + 1], fn y ->
...> chain([y + 2, y + 10], fn z ->
...> [x, y, z]
...> end)
...> end)
...> end)
[1, 2, 4, 1, 2, 12, 2, 3, 5, 2, 3, 13, 3, 4, 6, 3, 4, 14]
Specs
Compose link functions to create a new link function.
Note that this runs the same direction as <|>
("the math way").
This is pipe_compose_link/2
with arguments flipped.
Examples
iex> links =
...> fn x -> [x, x] end
...> |> compose_link(fn y -> [y * 10] end)
...> |> compose_link(fn z -> [z + 42] end)
...>
...> [1, 2, 3] >>> links
[430, 430, 440, 440, 450, 450]
Specs
chain/2
but with the arguments flipped.
Examples
iex> draw(fn x -> [x, x] end, [1, 2, 3])
[1, 1, 2, 2, 3, 3]
iex> (fn y -> [y * 5, y * 10] end)
...> |> draw((fn x -> [x, x] end)
...> |> draw([1, 2, 3])) # note the "extra" closing paren
[5, 10, 5, 10, 10, 20, 10, 20, 15, 30, 15, 30]
Specs
Specs
Join together one nested level of a data structure that contains itself
Examples
iex> join([[1, 2, 3]])
[1, 2, 3]
iex> join([[1, 2, 3], [4, 5, 6]])
[1, 2, 3, 4, 5, 6]
iex> join([[[1, 2, 3], [4, 5, 6]]])
[[1, 2, 3], [4, 5, 6]]
alias Algae.Maybe.{Nothing, Just}
%Just{
just: %Just{
just: 42
}
} |> join()
#=> %Just{just: 42}
join %Just{just: %Nothing{}}
#=> %Nothing{}
join %Just{just: %Just{just: %Nothing{}}}
#=> %Just{just: %Nothing{}}
%Nothing{} |> join() |> join() |> join() # ...and so on, forever
#=> %Nothing{}
Joining tuples is a bit counterintuitive, as it requires a very specific format:
iex> join { # Outer 2-tuple
...> {1, 2}, # Inner 2-tuple
...> {
...> {3, 4}, # Doubly inner 2-tuple
...> {5, 6, 7}
...> }
...> }
{{4, 6}, {5, 6, 7}}
iex> join {
...> {"a", "b"},
...> {
...> {"!", "?"},
...> {:ok, 123}
...> }
...> }
{{"a!", "b?"}, {:ok, 123}}
Specs
Compose link functions to create a new link function.
This is compose_link/2
with arguments flipped.
Examples
iex> links =
...> fn x -> [x, x] end
...> |> pipe_compose_link(fn y -> [y * 10] end)
...> |> pipe_compose_link(fn z -> [z + 42] end)
...>
...> [1, 2, 3] >>> links
[52, 52, 62, 62, 72, 72]