Skip to content

Commit

Permalink
improve docs and add for monads
Browse files Browse the repository at this point in the history
  • Loading branch information
slogsdon committed Oct 5, 2015
1 parent 5a9bef4 commit cbb62ae
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 3 deletions.
3 changes: 3 additions & 0 deletions lib/control/applicative.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
defprotocol Control.Applicative do
@moduledoc """
Applicatives (or more specifically applicative functors)
are a special form of `Control.Functor` where the value
within the functor is a function.
"""

@doc """
Expand Down
17 changes: 17 additions & 0 deletions lib/control/functor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@ defprotocol Control.Functor do
@moduledoc """
Functors are things that can be mapped over, like lists,
`Maybe`s, trees, and such.
Using functors, we can generalize how `Enum.map` works for
`Enumerable`s on any data type, including `Maybe`. We can
accomplish this by generalizing the action and implementing
that action for the desired data types. This generalized
action for functors is known as `fmap`.
## Laws
All implementations of `Control.Functor` should obey the
following implicit laws:
fmap(f, id) = f |> id
fmap(f, (p |> q)) = f |> fmap(p) |> fmap (q)
where `f` is a functor, `id` is a function that returns
its input, and `p` & `q` are functions.
"""

@doc """
Expand Down
23 changes: 22 additions & 1 deletion lib/control/helpers.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
defmodule Control.Helpers do
@moduledoc """
A set of helper functions to ease the process of
working with the `Control.*` protocols.
"""

alias Control.Monad

@compile {:inline, ~>>: 2}

@doc """
Bind operator.
The bind operator (`>>=` in Haskell) removes the need
for piping to a call to `Control.Monad.bind/2`, so
functor |> Control.Monad.bind(fun)
becomes
functor ~>> fun
"""
@spec (Monad.t ~>> (term -> Monad.t)) :: Monad.t
def left ~>> right do
Control.Monad.bind(left, right)
Monad.bind(left, right)
end
end
31 changes: 31 additions & 0 deletions lib/control/monad.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
defprotocol Control.Monad do
@moduledoc """
A monad is essentially a value with a context.
Just as applicative functors are special versions of
functors, monads are special versions of applicative
functors.
## Laws
All implementations of `Control.Monad` should obey the
following implicit laws:
type |> return(x) ~>> f = f(x)
m ~>> return = m
(m ~>> f) ~>> g = m ~>> &(f(&1) ~>> g)
where `m` is a monad, `x` is a value, and `f` and `g` are
both functions that return monadic values.
"""

@doc """
`return` takes a value and puts it in a minimal default
context that still holds that value. In other words, it
takes something and wraps it in a monad.
"""
@spec return(t) :: t
@spec return(t, term) :: t
def return(m, value \\ nil)

@doc """
`bind` is similar to function application, only instead of
taking a normal value and feeding it to a normal function,
it takes a monadic value (that is, a value with a context)
and feeds it to a function that takes a normal value but
returns a monadic value.
"""
@spec bind(t, (term -> t)) :: t
def bind(m, fun)
Expand Down
7 changes: 6 additions & 1 deletion lib/control/monad/maybe.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
defimpl Control.Monad, for: Data.Maybe do
def bind(%{nothing: true} = f, _), do: f
def return(m), do: m
def return(%{just: nil, nothing: false}, value) do
Data.Maybe.just(value)
end

def bind(%{nothing: true} = m, _), do: m
def bind(%{just: v}, fun) do
fun |> apply([v])
end
Expand Down
35 changes: 35 additions & 0 deletions lib/control/monoid.ex
Original file line number Diff line number Diff line change
@@ -1,13 +1,48 @@
defprotocol Control.Monoid do
@moduledoc """
A monoid is a function and an identity for a given type.
In more detail, a monoid is when you have an associative
binary function and a value which acts as an identity
with respect to that function. When something acts as an
identity with respect to a function, it means that when
called with that function and some other value, the
result is always equal to that other value. `1` is the
identity with respect to `*` and `[]` is the identity
with respect to `++`. There are a lot of other monoids
to be found in the world of Elixir, which is why the
`Control.Monoid` protocol exists. It's for types which
can act like monoids.
## Laws
All implementations of `Control.Monoid` should obey the
following implicit laws:
mempty(type) |> mappend(x) = x
x |> mappend(mempty(type)) = x
mappend(x, y) |> mappend(z) = x |> mappend(y |> mappend(z))
where `type` is a type and `x`, `y`, and `z` are all values
of that type.
"""

@doc """
The identity value.
It should act as a polymorphic constant, but due to
protocols needing a type for dispatching to the correct
implementation, it requires a type to be passed as an
argument.
"""
@spec mempty(t) :: t
def mempty(a)

@doc """
The binary function.
It takes two values of the same type and returns a
value of that type as well.
"""
@spec mappend(t, t) :: t
def mappend(a, b)
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ defmodule Control.Mixfile do

def project do
[app: :control,
version: "0.0.1",
version: "0.0.2",
elixir: "~> 1.0",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
test_coverage: [tool: ExCoveralls],
preferred_cli_env: [coveralls: :test],
description: description,
package: package,
deps: deps]
Expand Down

0 comments on commit cbb62ae

Please sign in to comment.