diff options
Diffstat (limited to 'lib/pure/options.nim')
-rw-r--r-- | lib/pure/options.nim | 532 |
1 files changed, 199 insertions, 333 deletions
diff --git a/lib/pure/options.nim b/lib/pure/options.nim index d56cf4767..b34ff72c0 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -7,67 +7,91 @@ # distribution, for details about the copyright. # -## This module implements types which encapsulate an optional value. -## -## A value of type `Option[T]` either contains a value `x` (represented as -## `some(x)`) or is empty (`none(T)`). -## -## This can be useful when you have a value that can be present or not. The -## absence of a value is often represented by `nil`, but it is not always -## available, nor is it always a good solution. -## -## -## Basic usage -## =========== -## -## Let's start with an example: a procedure that finds the index of a character -## in a string. -## -## .. code-block:: nim -## -## import options -## -## proc find(haystack: string, needle: char): Option[int] = -## for i, c in haystack: -## if c == needle: -## return some(i) -## return none(int) # This line is actually optional, -## # because the default is empty -## -## .. code-block:: nim -## -## let found = "abc".find('c') -## assert found.isSome and found.get() == 2 -## -## The `get` operation demonstrated above returns the underlying value, or -## raises `UnpackDefect` if there is no value. Note that `UnpackDefect` -## inherits from `system.Defect`, and should therefore never be caught. -## Instead, rely on checking if the option contains a value with -## `isSome <#isSome,Option[T]>`_ and `isNone <#isNone,Option[T]>`_ procs. -## -## How to deal with an absence of a value: -## -## .. code-block:: nim -## -## let result = "team".find('i') -## -## # Nothing was found, so the result is `none`. -## assert(result == none(int)) -## # It has no value: -## assert(result.isNone) - -import typetraits +##[ +This module implements types which encapsulate an optional value. + +A value of type `Option[T]` either contains a value `x` (represented as +`some(x)`) or is empty (`none(T)`). + +This can be useful when you have a value that can be present or not. The +absence of a value is often represented by `nil`, but that is not always +available, nor is it always a good solution. + + +Basic usage +=========== + +Let's start with an example: a procedure that finds the index of a character +in a string. +]## + +runnableExamples: + proc find(haystack: string, needle: char): Option[int] = + for i, c in haystack: + if c == needle: + return some(i) + return none(int) # This line is actually optional, + # because the default is empty + + let found = "abc".find('c') + assert found.isSome and found.get() == 2 + +##[ +The `get` operation demonstrated above returns the underlying value, or +raises `UnpackDefect` if there is no value. Note that `UnpackDefect` +inherits from `system.Defect` and should therefore never be caught. +Instead, rely on checking if the option contains a value with the +`isSome <#isSome,Option[T]>`_ and `isNone <#isNone,Option[T]>`_ procs. + + +Pattern matching +================ + +.. note:: This requires the [fusion](https://github.com/nim-lang/fusion) package. + +[fusion/matching](https://nim-lang.github.io/fusion/src/fusion/matching.html) +supports pattern matching on `Option`s, with the `Some(<pattern>)` and +`None()` patterns. + + ```nim + {.experimental: "caseStmtMacros".} + + import fusion/matching + + case some(42) + of Some(@a): + assert a == 42 + of None(): + assert false + + assertMatch(some(some(none(int))), Some(Some(None()))) + ``` +]## +# xxx pending https://github.com/timotheecour/Nim/issues/376 use `runnableExamples` and `whichModule` + +when defined(nimHasEffectsOf): + {.experimental: "strictEffects".} +else: + {.pragma: effectsOf.} + +import std/typetraits + +when defined(nimPreviewSlimSystem): + import std/assertions + when (NimMajor, NimMinor) >= (1, 1): type - SomePointer = ref | ptr | pointer | proc + SomePointer = ref | ptr | pointer | proc | iterator {.closure.} else: type SomePointer = ref | ptr | pointer type Option*[T] = object - ## An optional type that stores its value and state separately in a boolean. + ## An optional type that may or may not contain a value of type `T`. + ## When `T` is a a pointer type (`ptr`, `pointer`, `ref`, `proc` or `iterator {.closure.}`), + ## `none(T)` is represented as `nil`. when T is SomePointer: val: T else: @@ -77,77 +101,72 @@ type UnpackDefect* = object of Defect UnpackError* {.deprecated: "See corresponding Defect".} = UnpackDefect -proc option*[T](val: T): Option[T] {.inline.} = - ## Can be used to convert a pointer type (`ptr` or `ref` or `proc`) to an option type. - ## It converts `nil` to `None`. +proc option*[T](val: sink T): Option[T] {.inline.} = + ## Can be used to convert a pointer type (`ptr`, `pointer`, `ref` or `proc`) to an option type. + ## It converts `nil` to `none(T)`. When `T` is no pointer type, this is equivalent to `some(val)`. ## - ## See also: - ## * `some <#some,T>`_ - ## * `none <#none,typedesc>`_ + ## **See also:** + ## * `some proc <#some,T>`_ + ## * `none proc <#none,typedesc>`_ runnableExamples: type Foo = ref object a: int b: string - var c: Foo - assert c.isNil - var d = option(c) - assert d.isNone - result.val = val - when T isnot SomePointer: - result.has = true + assert option[Foo](nil).isNone + assert option(42).isSome + + when T is SomePointer: + result = Option[T](val: val) + else: + result = Option[T](has: true, val: val) -proc some*[T](val: T): Option[T] {.inline.} = +proc some*[T](val: sink T): Option[T] {.inline.} = ## Returns an `Option` that has the value `val`. ## - ## See also: - ## * `option <#option,T>`_ - ## * `none <#none,typedesc>`_ - ## * `isSome <#isSome,Option[T]>`_ + ## **See also:** + ## * `option proc <#option,T>`_ + ## * `none proc <#none,typedesc>`_ + ## * `isSome proc <#isSome,Option[T]>`_ runnableExamples: - var - a = some("abc") - b = some(42) - assert $type(a) == "Option[system.string]" - assert b.isSome + let a = some("abc") + + assert a.isSome assert a.get == "abc" - assert $b == "Some(42)" when T is SomePointer: - assert(not val.isNil) - result.val = val + assert not val.isNil + result = Option[T](val: val) else: - result.has = true - result.val = val + result = Option[T](has: true, val: val) proc none*(T: typedesc): Option[T] {.inline.} = ## Returns an `Option` for this type that has no value. ## - ## See also: - ## * `option <#option,T>`_ - ## * `some <#some,T>`_ - ## * `isNone <#isNone,Option[T]>`_ + ## **See also:** + ## * `option proc <#option,T>`_ + ## * `some proc <#some,T>`_ + ## * `isNone proc <#isNone,Option[T]>`_ runnableExamples: - var a = none(int) - assert a.isNone - assert $type(a) == "Option[system.int]" + assert none(int).isNone # the default is the none type - discard + result = Option[T]() proc none*[T]: Option[T] {.inline.} = - ## Alias for `none(T) proc <#none,typedesc>`_. + ## Alias for `none(T) <#none,typedesc>`_. none(T) proc isSome*[T](self: Option[T]): bool {.inline.} = ## Checks if an `Option` contains a value. + ## + ## **See also:** + ## * `isNone proc <#isNone,Option[T]>`_ + ## * `some proc <#some,T>`_ runnableExamples: - var - a = some(42) - b = none(string) - assert a.isSome - assert not b.isSome + assert some(42).isSome + assert not none(string).isSome when T is SomePointer: not self.val.isNil @@ -156,44 +175,40 @@ proc isSome*[T](self: Option[T]): bool {.inline.} = proc isNone*[T](self: Option[T]): bool {.inline.} = ## Checks if an `Option` is empty. + ## + ## **See also:** + ## * `isSome proc <#isSome,Option[T]>`_ + ## * `none proc <#none,typedesc>`_ runnableExamples: - var - a = some(42) - b = none(string) - assert not a.isNone - assert b.isNone + assert not some(42).isNone + assert none(string).isNone + when T is SomePointer: self.val.isNil else: not self.has proc get*[T](self: Option[T]): lent T {.inline.} = - ## Returns contents of an `Option`. If it is `None`, then an exception is - ## thrown. + ## Returns the content of an `Option`. If it has no value, + ## an `UnpackDefect` exception is raised. ## - ## See also: - ## * `get proc <#get,Option[T],T>`_ with the default return value + ## **See also:** + ## * `get proc <#get,Option[T],T>`_ with a default return value runnableExamples: - let - a = some(42) - b = none(string) - assert a.get == 42 + assert some(42).get == 42 doAssertRaises(UnpackDefect): - echo b.get + echo none(string).get if self.isNone: raise newException(UnpackDefect, "Can't obtain a value from a `none`") result = self.val proc get*[T](self: Option[T], otherwise: T): T {.inline.} = - ## Returns the contents of the `Option` or an `otherwise` value if - ## the `Option` is `None`. + ## Returns the content of the `Option` or `otherwise` if + ## the `Option` has no value. runnableExamples: - var - a = some(42) - b = none(int) - assert a.get(9999) == 42 - assert b.get(9999) == 9999 + assert some(42).get(9999) == 42 + assert none(int).get(9999) == 9999 if self.isSome: self.val @@ -201,13 +216,14 @@ proc get*[T](self: Option[T], otherwise: T): T {.inline.} = otherwise proc get*[T](self: var Option[T]): var T {.inline.} = - ## Returns contents of the `var Option`. If it is `None`, then an exception - ## is thrown. + ## Returns the content of the `var Option` mutably. If it has no value, + ## an `UnpackDefect` exception is raised. runnableExamples: - let + var a = some(42) b = none(string) - assert a.get == 42 + inc(a.get) + assert a.get == 43 doAssertRaises(UnpackDefect): echo b.get @@ -215,77 +231,68 @@ proc get*[T](self: var Option[T]): var T {.inline.} = raise newException(UnpackDefect, "Can't obtain a value from a `none`") return self.val -proc map*[T](self: Option[T], callback: proc (input: T)) {.inline.} = +proc map*[T](self: Option[T], callback: proc (input: T)) {.inline, effectsOf: callback.} = ## Applies a `callback` function to the value of the `Option`, if it has one. ## - ## See also: + ## **See also:** ## * `map proc <#map,Option[T],proc(T)_2>`_ for a version with a callback ## which returns a value - ## * `filter proc <#filter,Option[T],proc(T)>`_ runnableExamples: var d = 0 proc saveDouble(x: int) = - d = 2*x + d = 2 * x - let - a = some(42) - b = none(int) - - b.map(saveDouble) + none(int).map(saveDouble) assert d == 0 - a.map(saveDouble) + some(42).map(saveDouble) assert d == 84 if self.isSome: callback(self.val) -proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] {.inline.} = +proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] {.inline, effectsOf: callback.} = ## Applies a `callback` function to the value of the `Option` and returns an ## `Option` containing the new value. ## - ## If the `Option` is `None`, `None` of the return type of the `callback` - ## will be returned. + ## If the `Option` has no value, `none(R)` will be returned. ## - ## See also: - ## * `flatMap proc <#flatMap,Option[A],proc(A)>`_ for a version with a - ## callback which returns an `Option` - ## * `filter proc <#filter,Option[T],proc(T)>`_ + ## **See also:** + ## * `map proc <#map,Option[T],proc(T)>`_ + ## * `flatMap proc <#flatMap,Option[T],proc(T)>`_ for a version with a + ## callback that returns an `Option` runnableExamples: - var - a = some(42) - b = none(int) - proc isEven(x: int): bool = x mod 2 == 0 - assert $(a.map(isEven)) == "Some(true)" - assert $(b.map(isEven)) == "None[bool]" + assert some(42).map(isEven) == some(true) + assert none(int).map(isEven) == none(bool) if self.isSome: some[R](callback(self.val)) else: none(R) -proc flatten*[A](self: Option[Option[A]]): Option[A] {.inline.} = +proc flatten*[T](self: Option[Option[T]]): Option[T] {.inline.} = ## Remove one level of structure in a nested `Option`. + ## + ## **See also:** + ## * `flatMap proc <#flatMap,Option[T],proc(T)>`_ runnableExamples: - let a = some(some(42)) - assert $flatten(a) == "Some(42)" + assert flatten(some(some(42))) == some(42) + assert flatten(none(Option[int])) == none(int) if self.isSome: self.val else: - none(A) + none(T) -proc flatMap*[A, B](self: Option[A], - callback: proc (input: A): Option[B]): Option[B] {.inline.} = - ## Applies a `callback` function to the value of the `Option` and returns an - ## `Option` containing the new value. +proc flatMap*[T, R](self: Option[T], + callback: proc (input: T): Option[R]): Option[R] {.inline, effectsOf: callback.} = + ## Applies a `callback` function to the value of the `Option` and returns the new value. ## - ## If the `Option` is `None`, `None` of the return type of the `callback` - ## will be returned. + ## If the `Option` has no value, `none(R)` will be returned. ## - ## Similar to `map`, with the difference that the `callback` returns an + ## This is similar to `map`, with the difference that the `callback` returns an ## `Option`, not a raw value. This allows multiple procs with a ## signature of `A -> Option[B]` to be chained together. ## @@ -295,47 +302,40 @@ proc flatMap*[A, B](self: Option[A], runnableExamples: proc doublePositives(x: int): Option[int] = if x > 0: - return some(2*x) + some(2 * x) else: - return none(int) - let - a = some(42) - b = none(int) - c = some(-11) - assert a.flatMap(doublePositives) == some(84) - assert b.flatMap(doublePositives) == none(int) - assert c.flatMap(doublePositives) == none(int) + none(int) + + assert some(42).flatMap(doublePositives) == some(84) + assert none(int).flatMap(doublePositives) == none(int) + assert some(-11).flatMap(doublePositives) == none(int) map(self, callback).flatten() -proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] {.inline.} = +proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] {.inline, effectsOf: callback.} = ## Applies a `callback` to the value of the `Option`. ## - ## If the `callback` returns `true`, the option is returned as `Some`. - ## If it returns `false`, it is returned as `None`. + ## If the `callback` returns `true`, the option is returned as `some`. + ## If it returns `false`, it is returned as `none`. ## - ## See also: - ## * `map proc <#map,Option[T],proc(T)_2>`_ + ## **See also:** ## * `flatMap proc <#flatMap,Option[A],proc(A)>`_ runnableExamples: proc isEven(x: int): bool = x mod 2 == 0 - let - a = some(42) - b = none(int) - c = some(-11) - assert a.filter(isEven) == some(42) - assert b.filter(isEven) == none(int) - assert c.filter(isEven) == none(int) + + assert some(42).filter(isEven) == some(42) + assert none(int).filter(isEven) == none(int) + assert some(-11).filter(isEven) == none(int) if self.isSome and not callback(self.val): none(T) else: self -proc `==`*(a, b: Option): bool {.inline.} = - ## Returns `true` if both `Option`s are `None`, - ## or if they are both `Some` and have equal values. +proc `==`*[T](a, b: Option[T]): bool {.inline.} = + ## Returns `true` if both `Option`s are `none`, + ## or if they are both `some` and have equal values. runnableExamples: let a = some(42) @@ -347,169 +347,35 @@ proc `==`*(a, b: Option): bool {.inline.} = assert b == d assert not (a == b) - (a.isSome and b.isSome and a.val == b.val) or (not a.isSome and not b.isSome) + when T is SomePointer: + a.val == b.val + else: + (a.isSome and b.isSome and a.val == b.val) or (a.isNone and b.isNone) proc `$`*[T](self: Option[T]): string = ## Get the string representation of the `Option`. - ## - ## If the `Option` has a value, the result will be `Some(x)` where `x` - ## is the string representation of the contained value. - ## If the `Option` does not have a value, the result will be `None[T]` - ## where `T` is the name of the type contained in the `Option`. + runnableExamples: + assert $some(42) == "some(42)" + assert $none(int) == "none(int)" + if self.isSome: - result = "Some(" + when defined(nimLagacyOptionsDollar): + result = "Some(" + else: + result = "some(" result.addQuoted self.val result.add ")" else: - result = "None[" & name(T) & "]" + when defined(nimLagacyOptionsDollar): + result = "None[" & name(T) & "]" + else: + result = "none(" & name(T) & ")" -proc unsafeGet*[T](self: Option[T]): T {.inline.}= - ## Returns the value of a `some`. Behavior is undefined for `none`. +proc unsafeGet*[T](self: Option[T]): lent T {.inline.}= + ## Returns the value of a `some`. The behavior is undefined for `none`. ## - ## **Note:** Use it only when you are **absolutely sure** the value is present - ## (e.g. after checking `isSome <#isSome,Option[T]>`_). - ## Generally, using `get proc <#get,Option[T]>`_ is preferred. + ## **Note:** Use this only when you are **absolutely sure** the value is present + ## (e.g. after checking with `isSome <#isSome,Option[T]>`_). + ## Generally, using the `get proc <#get,Option[T]>`_ is preferred. assert self.isSome - self.val - - -when isMainModule: - import unittest, sequtils - - # RefPerson is used to test that overloaded `==` operator is not called by - # options. It is defined here in the global scope, because otherwise the test - # will not even consider the `==` operator. Different bug? - type RefPerson = ref object - name: string - - proc `==`(a, b: RefPerson): bool = - assert(not a.isNil and not b.isNil) - a.name == b.name - - suite "options": - # work around a bug in unittest - let intNone = none(int) - let stringNone = none(string) - - test "example": - proc find(haystack: string, needle: char): Option[int] = - for i, c in haystack: - if c == needle: - return some i - - check("abc".find('c').get() == 2) - - let result = "team".find('i') - - check result == intNone - check result.isNone - - test "some": - check some(6).get() == 6 - check some("a").unsafeGet() == "a" - check some(6).isSome - check some("a").isSome - - test "none": - expect UnpackDefect: - discard none(int).get() - check(none(int).isNone) - check(not none(string).isSome) - - test "equality": - check some("a") == some("a") - check some(7) != some(6) - check some("a") != stringNone - check intNone == intNone - - when compiles(some("a") == some(5)): - check false - when compiles(none(string) == none(int)): - check false - - test "get with a default value": - check(some("Correct").get("Wrong") == "Correct") - check(stringNone.get("Correct") == "Correct") - - test "$": - check($(some("Correct")) == "Some(\"Correct\")") - check($(stringNone) == "None[string]") - - test "map with a void result": - var procRan = 0 - some(123).map(proc (v: int) = procRan = v) - check procRan == 123 - intNone.map(proc (v: int) = check false) - - test "map": - check(some(123).map(proc (v: int): int = v * 2) == some(246)) - check(intNone.map(proc (v: int): int = v * 2).isNone) - - test "filter": - check(some(123).filter(proc (v: int): bool = v == 123) == some(123)) - check(some(456).filter(proc (v: int): bool = v == 123).isNone) - check(intNone.filter(proc (v: int): bool = check false).isNone) - - test "flatMap": - proc addOneIfNotZero(v: int): Option[int] = - if v != 0: - result = some(v + 1) - else: - result = none(int) - - check(some(1).flatMap(addOneIfNotZero) == some(2)) - check(some(0).flatMap(addOneIfNotZero) == none(int)) - check(some(1).flatMap(addOneIfNotZero).flatMap(addOneIfNotZero) == some(3)) - - proc maybeToString(v: int): Option[string] = - if v != 0: - result = some($v) - else: - result = none(string) - - check(some(1).flatMap(maybeToString) == some("1")) - - proc maybeExclaim(v: string): Option[string] = - if v != "": - result = some v & "!" - else: - result = none(string) - - check(some(1).flatMap(maybeToString).flatMap(maybeExclaim) == some("1!")) - check(some(0).flatMap(maybeToString).flatMap(maybeExclaim) == none(string)) - - test "SomePointer": - var intref: ref int - check(option(intref).isNone) - intref.new - check(option(intref).isSome) - - let tmp = option(intref) - check(sizeof(tmp) == sizeof(ptr int)) - - var prc = proc (x: int): int = x + 1 - check(option(prc).isSome) - prc = nil - check(option(prc).isNone) - - test "none[T]": - check(none[int]().isNone) - check(none(int) == none[int]()) - - test "$ on typed with .name": - type Named = object - name: string - - let nobody = none(Named) - check($nobody == "None[Named]") - - test "$ on type with name()": - type Person = object - myname: string - - let noperson = none(Person) - check($noperson == "None[Person]") - - test "Ref type with overloaded `==`": - let p = some(RefPerson.new()) - check p.isSome + result = self.val |