-
Notifications
You must be signed in to change notification settings - Fork 9
/
option.nim
210 lines (173 loc) · 5.45 KB
/
option.nim
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
import sugar,
strutils,
classy,
./kleisli,
./function
{.experimental.}
type
OptionKind = enum
okNone, okSome
Option*[T] = ref object
## Option ADT
case kind: OptionKind
of okSome:
value: T
else:
discard
proc Some*[T](value: T): Option[T] =
## Constructs option object with value
Option[T](kind: okSome, value: value)
proc None*[T](): Option[T] =
## Constructs empty option object
Option[T](kind: okNone)
# Some helpers
proc some*[T](value: T): Option[T] = Some(value)
proc none*[T](value: T): Option[T] = None[T]()
proc none*(T: typedesc): Option[T] = None[T]()
proc notNil*[T](o: Option[T]): Option[T] =
## Maps nil object to none
if o.kind == okSome and o.value.isNil:
none(T)
else:
o
proc notEmpty*(o: Option[string]): Option[string] =
## Maps empty string to none
if o.kind == okSome and (o.value.strip == ""):
string.none
else:
o
proc option*[T](p: bool, v: T): Option[T] =
## Returns the boxed value of `v` if ``p == true`` or None
if p: v.some
else: T.none
proc optionF*[T](p: bool, f: () -> T): Option[T] =
## Returns the boxed value of ``f()`` if ``p == true`` or None
if p: f().some
else: T.none
proc `==`*[T](x, y: Option[T]): bool =
if x.isDefined and y.isDefined:
x.value == y.value
elif x.isEmpty and y.isEmpty:
true
else:
false
proc isEmpty*[T](o: Option[T]): bool =
## Checks if `o` is empty
o.kind == okNone
proc isDefined*[T](o: Option[T]): bool =
## Checks if `o` contains value
not o.isEmpty
proc `$`*[T](o: Option[T]): string =
## Returns string representation of `o`
if o.isDefined:
"Some(" & $o.value & ")"
else:
"None"
proc map*[T,U](o: Option[T], f: T -> U): Option[U] =
## Returns option with result of applying f to the value of `o` if it exists
if o.isDefined:
f(o.value).some
else:
none(U)
proc flatMap*[T,U](o: Option[T], f: T -> Option[U]): Option[U] =
## Returns the result of applying `f` if `o` is defined, or none
if o.isDefined: f(o.value) else: none(U)
proc join*[T](mmt: Option[Option[T]]): Option[T] =
## Flattens the option
mmt.flatMap(id)
proc get*[T](o: Option[T]): T =
## Returns option's value if defined, or fail
doAssert(o.isDefined, "Can't get Option's value")
o.value
proc getOrElse*[T](o: Option[T], d: T): T =
## Returns option's value if defined, or `d`
if o.isDefined: o.value else: d
proc getOrElse*[T](o: Option[T], f: void -> T): T =
## Returns option's value if defined, or the result of applying `f`
if o.isDefined: o.value else: f()
proc orElse*[T](o: Option[T], d: Option[T]): Option[T] =
## Returns `o` if defined, or `d`
if o.isDefined: o else: d
proc orElse*[T](o: Option[T], f: void -> Option[T]): Option[T] =
## Returns `o` if defined, or the result of applying `f`
if o.isDefined: o else: f()
proc filter*[T](o: Option[T], p: T -> bool): Option[T] =
## Returns `o` if it is defined and the result of applying `p`
## to it's value is true
if o.isDefined and p(o.value): o else: none(T)
proc map2*[T,U,V](t: Option[T], u: Option[U], f: (T, U) -> V): Option[V] =
## Returns the result of applying f to `t` and `u` value if they are both defined
if t.isDefined and u.isDefined: f(t.value, u.value).some else: none(V)
proc map2F*[A, B, C](
ma: Option[A],
mb: () -> Option[B],
f: (A, B) -> C
): Option[C] =
## Maps 2 `Option` values via `f`. Lazy in second argument.
ma.flatMap((a: A) => mb().map((b: B) => f(a, b)))
proc zip*[T, U](t: Option[T], u: Option[U]): Option[(T, U)] =
## Returns the tuple of `t` and `u` values if they are both defined
if t.isDefined and u.isDefined:
(t.get, u.get).some
else:
none((T, U))
proc liftO*[T,U](f: T -> U): proc(o: Option[T]): Option[U] =
## Turns the function `f` of type `T -> U` into the function
## of type `Option[T] -> Option[U]`
(o: Option[T]) => o.map((x: T) => f(x))
proc forEach*[T](xs: Option[T], f: T -> void): void =
## Applies `f` to the options value if it's defined
if xs.isDefined:
f(xs.value)
proc forAll*[T](xs: Option[T], f: T -> bool): bool =
## Returns `f` applied to the option's value or true
if xs.isDefined:
f(xs.value)
else:
true
proc traverse*[T, U](ts: seq[T], f: T -> Option[U]): Option[seq[U]] =
## Returns list of values of application of `f` to elements in `ts`
## if all the results are defined
##
## Example:
## .. code-block:: nim
## traverse(@[1, 2, 3], (t: int) => (t - 1).some) == @[0, 1, 2].some
##
## let f = (t: int) => (if (t < 3): t.some else: int.none)
## traverse(@[1, 2, 3], f) == seq[int].none
var acc = newSeq[U](ts.len)
for i, t in ts:
let mu = f(t)
if mu.isDefined:
acc[i] = mu.get
else:
return none(seq[U])
return acc.some
proc asSeq*[T](o: Option[T]): seq[T] =
if o.isDefined:
@[o.get]
else:
@[]
template elemType*(v: Option): typedesc =
## Part of ``do notation`` contract
type(v.get)
proc point*[A](v: A, t: typedesc[Option[A]]): Option[A] =
v.some
instance KleisliInst, Option[_], exporting(_)
proc fold*[A,B](v: Option[A], ifNone: () -> B, ifSome: A -> B): B =
## Returns the result of applying `ifSome` to the value if `v` is
## defined. Otherwise evaluates `ifNone`.
if v.isDefined:
ifSome(v.value)
else:
ifNone()
proc foldLeft*[A,B](v: Option[A], b: B, f: (B, A) -> B): B =
if v.isDefined:
f(b, v.value)
else:
b
proc foldRight*[A,B](v: Option[A], b: B, f: (A, B) -> B): B =
if v.isDefined:
f(v.value, b)
else:
b