Skip to content

Commit ee24d96

Browse files
committed
Extend options stdlib
1 parent 10c9eba commit ee24d96

File tree

2 files changed

+198
-0
lines changed

2 files changed

+198
-0
lines changed

Diff for: lib/pure/options.nim

+139
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,142 @@ proc unsafeGet*[T](self: Option[T]): lent T {.inline.}=
379379
## Generally, using the `get proc <#get,Option[T]>`_ is preferred.
380380
assert self.isSome
381381
result = self.val
382+
383+
template withValue*[T](source: Option[T]; varname, ifExists, ifAbsent: untyped) =
384+
## Reads a value from an Option, assigns it to a variable, and calls `ifExists` when it is `some`.
385+
## If the value is `none`, it calls `ifAbsent`.
386+
runnableExamples:
387+
some("abc").withValue(foo):
388+
assert foo == "abc"
389+
do:
390+
assert false
391+
392+
var absentCalled: bool
393+
none(int).withValue(foo):
394+
assert false
395+
do:
396+
absentCalled = true
397+
assert absentCalled
398+
399+
let local = source
400+
if local.isSome:
401+
let varname {.inject, used.} = unsafeGet(local)
402+
ifExists
403+
else:
404+
ifAbsent
405+
406+
template withValue*[T](source: Option[T]; varname, ifExists: untyped) =
407+
## Reads a value from an Option, assigns it to a variable, and calls `ifExists` when it is `some`.
408+
runnableExamples:
409+
some("abc").withValue(foo):
410+
assert foo == "abc"
411+
412+
none(int).withValue(foo):
413+
assert false
414+
415+
source.withValue(varname, ifExists):
416+
discard
417+
418+
template mapIt*[T](value: Option[T], action: untyped): untyped =
419+
## Applies an action to the value of the `Option`, if it has one.
420+
runnableExamples:
421+
assert some(42).mapIt(it * 2).mapIt($it) == some("84")
422+
assert none(int).mapIt(it * 2).mapIt($it) == none(string)
423+
424+
block:
425+
type InnerType = typeof(
426+
block:
427+
var it {.inject, used.}: typeof(value.get())
428+
action
429+
)
430+
431+
var outcome: Option[InnerType]
432+
value.withValue(it):
433+
outcome = some(action)
434+
outcome
435+
436+
template flatMapIt*[T](value: Option[T], action: untyped): untyped =
437+
## Executes an action on the value of the `Option`, where that action can also return an `Option`.
438+
runnableExamples:
439+
assert some(42).flatMapIt(some($it)) == some("42")
440+
assert some(42).flatMapIt(none(string)) == none(string)
441+
assert none(int).flatMapIt(some($it)) == none(string)
442+
assert none(int).flatMapIt(none(string)) == none(string)
443+
444+
block:
445+
type InnerType = typeof(
446+
block:
447+
var it {.inject, used.}: typeof(value.get())
448+
action.get()
449+
)
450+
451+
var outcome: Option[InnerType]
452+
value.withValue(it):
453+
outcome = action
454+
outcome
455+
456+
template filterIt*[T](value: Option[T], action: untyped): Option[T] =
457+
## Tests the value of the `Option` with a predicate, returning a `none` if it fails.
458+
runnableExamples:
459+
assert some(42).filterIt(it > 0) == some(42)
460+
assert none(int).filterIt(it > 0) == none(int)
461+
assert some(-11).filterIt(it > 0) == none(int)
462+
463+
block:
464+
var outcome = value
465+
outcome.withValue(it):
466+
if not action:
467+
outcome = none(T)
468+
do:
469+
outcome = none(T)
470+
outcome
471+
472+
template applyIt*[T](value: Option[T], action: untyped) =
473+
## Executes a code block if the `Option` is `some`, assigning the value to a variable named `it`
474+
runnableExamples:
475+
var value: string
476+
some("foo").applyIt:
477+
value = it
478+
assert value == "foo"
479+
480+
none(string).applyIt:
481+
assert false
482+
483+
value.withValue(it):
484+
action
485+
486+
template valueOr*[T](value: Option[T], otherwise: untyped): T =
487+
## Returns the value in an option if it is set. Otherwise, executes a code block. This is
488+
## useful for executing side effects when the option is empty.
489+
runnableExamples:
490+
let a = some("foo").valueOr:
491+
assert false
492+
assert a == "foo"
493+
494+
let b = none(string).valueOr:
495+
"bar"
496+
assert b == "bar"
497+
498+
block:
499+
var outcome: T
500+
value.withValue(it):
501+
outcome = it
502+
do:
503+
when typeof(otherwise) is T:
504+
outcome = otherwise
505+
else:
506+
otherwise
507+
outcome
508+
509+
template `or`*[T](a, b: Option[T]): Option[T] =
510+
## Returns the value of the `Option` if it has one, otherwise returns the other `Option`.
511+
runnableExamples:
512+
assert((some(42) or some(9999)) == some(42))
513+
assert((none(int) or some(9999)) == some(9999))
514+
assert((none(int) or none(int)) == none(int))
515+
block:
516+
let local = a
517+
if local.isSome:
518+
local
519+
else:
520+
b

Diff for: tests/stdlib/toptions.nim

+59
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ template disableJsVm(body) =
2828
else:
2929
body
3030

31+
proc buildOnce[T](value: T): proc(): T =
32+
var called = false
33+
return proc(): T =
34+
doAssert(not called, "Expression should only be executed once")
35+
called = true
36+
return value
37+
3138
proc main() =
3239
type
3340
Foo = ref object
@@ -197,6 +204,58 @@ proc main() =
197204
doAssert x.isNone
198205
doAssert $x == "none(cstring)"
199206

207+
# withValue should only evaluate the expression once
208+
block:
209+
let someValue = buildOnce(some(42))
210+
someValue().withValue(value):
211+
doAssert(value == 42)
212+
do:
213+
doAssert false
214+
215+
# withValue should only evaluate the expression once
216+
block:
217+
let someValue = buildOnce(some(42))
218+
someValue().withValue(value):
219+
doAssert(value == 42)
220+
221+
# mapIt should only evalute its expression once
222+
block:
223+
let someValue = buildOnce(some(42))
224+
doAssert someValue().mapIt($it) == some("42")
225+
226+
# flatMapIt should only evalute its expression once
227+
block:
228+
let someValue = buildOnce(some(42))
229+
doAssert someValue().flatMapIt(some($it)) == some("42")
230+
231+
# filterIt should only evaluate its expression once
232+
block:
233+
let someValue = buildOnce(some(42))
234+
var outcome: int
235+
someValue().applyIt:
236+
outcome = it
237+
doAssert outcome == 42
238+
239+
# valueOr should only evaluate its expression once
240+
block:
241+
let a = buildOnce(some(42))
242+
doAssert a().valueOr(0) == 42
243+
244+
let b = buildOnce(none(int))
245+
doAssert b().valueOr(0) == 0
246+
247+
let c = buildOnce(some(42))
248+
discard c().valueOr:
249+
doAssert false
250+
251+
# or should only evaluate its expression once
252+
block:
253+
let a = buildOnce(some(42))
254+
doAssert a().or(some(0)) == some(42)
255+
256+
let b = buildOnce(some(42))
257+
doAssert none(int).or(b()) == some(42)
258+
200259
static: main()
201260
main()
202261

0 commit comments

Comments
 (0)