/
maybe.ex
108 lines (82 loc) · 2.46 KB
/
maybe.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
defmodule Monad.Maybe do
@moduledoc """
A monad that represents something or nothing.
The concept of having something vs. nothing is similar to having a value vs.
`nil`.
"""
@typedoc """
The possible types of values that can occur (i.e. something and nothing).
"""
@type maybe_type :: :some | :none
use Monad.Behaviour
@opaque t :: %__MODULE__{type: maybe_type, value: term}
@doc false
defstruct type: :none, value: nil
@doc """
Returns a "nothing" state.
"""
defmacro none, do: quote(do: %Monad.Maybe{})
@doc """
Wraps the value into a maybe monad.
iex> some 42
%Monad.Maybe{type: :some, value: 42}
"""
@spec some(term) :: t
def some(value), do: %Monad.Maybe{type: :some, value: value}
@doc """
An alias for `some/1`.
"""
@spec pure(term) :: t
def pure(value), do: some(value)
@doc """
Unwraps the value from a maybe monad.
Does not work with `none` values, since they contain nothing.
iex> m = some 42
iex> unwrap! m
42
"""
@spec unwrap!(t) :: term
def unwrap!(%Monad.Maybe{type: :some, value: value}), do: value
@doc """
Macro that indicates if the maybe monad contains nothing.
This macro may be used in guard clauses.
iex> none? none()
true
iex> none? some 42
false
"""
defmacro none?(maybe), do: quote(do: unquote(maybe) == none())
@doc """
Macro that indicates if the maybe monad contains something.
This macro may be used in guard clauses.
iex> some? none()
false
iex> some? some 42
true
"""
defmacro some?(maybe), do: quote(do: not none?(unquote(maybe)))
@doc """
Callback implementation of `Monad.Behaviour.return/1`.
Wraps the value in a maybe monad.
iex> return 42
%Monad.Maybe{type: :some, value: 42}
"""
@spec return(term) :: t
def return(value), do: pure(value)
@doc """
Callback implementation of `Monad.Behaviour.bind/2`.
If the monad contains a value, then the value is unwrapped and applied to
`fun`.
For `none` monads, `none` is returned without evaluating `fun`.
iex> m = some 42
iex> bind m, (& some &1 * 2)
%Monad.Maybe{type: :some, value: 84}
iex> bind none(), (& some &1 * 2)
%Monad.Maybe{type: :none, value: nil}
"""
@spec bind(t, (term -> t)) :: t
def bind(maybe, fun) when none?(maybe) and is_function(fun, 1), do: maybe
def bind(maybe, fun) when some?(maybe) and is_function(fun, 1) do
maybe |> unwrap! |> fun.()
end
end