diff options
Diffstat (limited to 'lib/pure/options.nim')
-rw-r--r-- | lib/pure/options.nim | 513 |
1 files changed, 336 insertions, 177 deletions
diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 2abb80016..b34ff72c0 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -7,216 +7,375 @@ # distribution, for details about the copyright. # -## Abstract -## ======== -## -## 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. -## -## -## Tutorial -## ======== -## -## 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 -## -## try: -## assert("abc".find('c').get() == 2) # Immediately extract the value -## except UnpackError: # If there is no value -## assert false # This will not be reached, because the value is present -## -## The ``get`` operation demonstrated above returns the underlying value, or -## raises ``UnpackError`` if there is no value. There is another option for -## obtaining the value: ``unsafeGet``, but you must only use it when you are -## absolutely sure the value is present (e.g. after checking ``isSome``). If -## you do not care about the tiny overhead that ``get`` causes, you should -## simply never use ``unsafeGet``. -## -## 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) -## -## try: -## echo result.get() -## assert(false) # This will not be reached -## except UnpackError: # Because an exception is raised -## discard - -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)`). -type - Option*[T] = object - ## An optional type that stores its value and state separately in a boolean. - val: T - has: bool - UnpackError* = ref object of ValueError +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. -proc some*[T](val: T): Option[T] = - ## Returns a ``Option`` that has this value. - result.has = true - result.val = val +Basic usage +=========== -proc none*(T: typedesc): Option[T] = - ## Returns a ``Option`` for this type that has no value. - result.has = false +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 -proc isSome*[T](self: Option[T]): bool = - self.has + let found = "abc".find('c') + assert found.isSome and found.get() == 2 -proc isNone*[T](self: Option[T]): bool = - not self.has +##[ +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. -proc unsafeGet*[T](self: Option[T]): T = - ## Returns the value of a ``some``. Behavior is undefined for ``none``. - assert self.isSome - self.val +Pattern matching +================ -proc get*[T](self: Option[T]): T = - ## Returns contents of the Option. If it is none, then an exception is - ## thrown. - if self.isNone: - raise UnpackError(msg : "Can't obtain a value from a `none`") - self.val +.. note:: This requires the [fusion](https://github.com/nim-lang/fusion) package. -proc get*[T](self: Option[T], otherwise: T): T = - ## Returns the contents of this option or `otherwise` if the option is none. - if self.isSome: - self.val - else: - otherwise +[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".} -proc map*[T](self: Option[T], callback: proc (input: T)) = - ## Applies a callback to the value in this Option - if self.has: - callback(self.val) - -proc map*[T, R](self: Option[T], callback: proc (input: T): R): Option[R] = - ## Applies a callback to the value in this Option and returns an option - ## containing the new value. If this option is None, None will be returned - if self.has: - some[R]( callback(self.val) ) - else: - none(R) + import fusion/matching -proc filter*[T](self: Option[T], callback: proc (input: T): bool): Option[T] = - ## Applies a callback to the value in this Option. If the callback returns - ## `true`, the option is returned as a Some. If it returns false, it is - ## returned as a None. - if self.has and not callback(self.val): - none(T) - else: - self + 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` -proc `==`*(a, b: Option): bool = - ## Returns ``true`` if both ``Option``s are ``none``, - ## or if they have equal values - (a.has and b.has and a.val == b.val) or (not a.has and not b.has) +when defined(nimHasEffectsOf): + {.experimental: "strictEffects".} +else: + {.pragma: effectsOf.} +import std/typetraits -proc `$`*[T]( self: Option[T] ): string = - ## Returns the contents of this option or `otherwise` if the option is none. - if self.has: - "Some(" & $self.val & ")" - else: - "None[" & T.name & "]" +when defined(nimPreviewSlimSystem): + import std/assertions -when isMainModule: - import unittest, sequtils +when (NimMajor, NimMinor) >= (1, 1): + type + SomePointer = ref | ptr | pointer | proc | iterator {.closure.} +else: + type + SomePointer = ref | ptr | pointer - suite "options": - # work around a bug in unittest - let intNone = none(int) - let stringNone = none(string) +type + Option*[T] = object + ## 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: + val: T + has: bool + + UnpackDefect* = object of Defect + UnpackError* {.deprecated: "See corresponding Defect".} = UnpackDefect + +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 proc <#some,T>`_ + ## * `none proc <#none,typedesc>`_ + runnableExamples: + type + Foo = ref object + a: int + b: string + + 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: sink T): Option[T] {.inline.} = + ## Returns an `Option` that has the value `val`. + ## + ## **See also:** + ## * `option proc <#option,T>`_ + ## * `none proc <#none,typedesc>`_ + ## * `isSome proc <#isSome,Option[T]>`_ + runnableExamples: + let a = some("abc") + + assert a.isSome + assert a.get == "abc" + + when T is SomePointer: + assert not val.isNil + result = Option[T](val: val) + else: + 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 proc <#option,T>`_ + ## * `some proc <#some,T>`_ + ## * `isNone proc <#isNone,Option[T]>`_ + runnableExamples: + assert none(int).isNone + + # the default is the none type + result = Option[T]() + +proc none*[T]: Option[T] {.inline.} = + ## 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: + assert some(42).isSome + assert not none(string).isSome + + when T is SomePointer: + not self.val.isNil + else: + self.has + +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: + 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 the content of an `Option`. If it has no value, + ## an `UnpackDefect` exception is raised. + ## + ## **See also:** + ## * `get proc <#get,Option[T],T>`_ with a default return value + runnableExamples: + assert some(42).get == 42 + doAssertRaises(UnpackDefect): + echo none(string).get - test "example": - proc find(haystack: string, needle: char): Option[int] = - for i, c in haystack: - if c == needle: - return some i + if self.isNone: + raise newException(UnpackDefect, "Can't obtain a value from a `none`") + result = self.val - check("abc".find('c').get() == 2) +proc get*[T](self: Option[T], otherwise: T): T {.inline.} = + ## Returns the content of the `Option` or `otherwise` if + ## the `Option` has no value. + runnableExamples: + assert some(42).get(9999) == 42 + assert none(int).get(9999) == 9999 - let result = "team".find('i') + if self.isSome: + self.val + else: + otherwise - check result == intNone - check result.isNone +proc get*[T](self: var Option[T]): var T {.inline.} = + ## Returns the content of the `var Option` mutably. If it has no value, + ## an `UnpackDefect` exception is raised. + runnableExamples: + var + a = some(42) + b = none(string) + inc(a.get) + assert a.get == 43 + doAssertRaises(UnpackDefect): + echo b.get - test "some": - check some(6).get() == 6 - check some("a").unsafeGet() == "a" - check some(6).isSome - check some("a").isSome + if self.isNone: + 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, effectsOf: callback.} = + ## Applies a `callback` function to the value of the `Option`, if it has one. + ## + ## **See also:** + ## * `map proc <#map,Option[T],proc(T)_2>`_ for a version with a callback + ## which returns a value + runnableExamples: + var d = 0 + proc saveDouble(x: int) = + d = 2 * x + + none(int).map(saveDouble) + assert d == 0 + some(42).map(saveDouble) + assert d == 84 - test "none": - expect UnpackError: - discard none(int).get() - check(none(int).isNone) - check(not none(string).isSome) + if self.isSome: + callback(self.val) - test "equality": - check some("a") == some("a") - check some(7) != some(6) - check some("a") != stringNone - check intNone == intNone +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` has no value, `none(R)` will be returned. + ## + ## **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: + proc isEven(x: int): bool = + x mod 2 == 0 + + assert some(42).map(isEven) == some(true) + assert none(int).map(isEven) == none(bool) - when compiles(some("a") == some(5)): - check false - when compiles(none(string) == none(int)): - check false + if self.isSome: + some[R](callback(self.val)) + else: + none(R) - test "get with a default value": - check( some("Correct").get("Wrong") == "Correct" ) - check( stringNone.get("Correct") == "Correct" ) +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: + assert flatten(some(some(42))) == some(42) + assert flatten(none(Option[int])) == none(int) - test "$": - check( $(some("Correct")) == "Some(Correct)" ) - check( $(stringNone) == "None[string]" ) + if self.isSome: + self.val + else: + none(T) - 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) +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` has no value, `none(R)` will be returned. + ## + ## 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. + ## + ## See also: + ## * `flatten proc <#flatten,Option[Option[A]]>`_ + ## * `filter proc <#filter,Option[T],proc(T)>`_ + runnableExamples: + proc doublePositives(x: int): Option[int] = + if x > 0: + some(2 * x) + else: + 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, 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`. + ## + ## **See also:** + ## * `flatMap proc <#flatMap,Option[A],proc(A)>`_ + runnableExamples: + proc isEven(x: int): bool = + x mod 2 == 0 + + 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 - test "map": - check( some(123).map(proc (v: int): int = v * 2) == some(246) ) - check( intNone.map(proc (v: int): int = v * 2).isNone ) +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) + b = none(int) + c = some(42) + d = none(int) + + assert a == c + assert b == d + assert not (a == b) + + 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) - 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 ) +proc `$`*[T](self: Option[T]): string = + ## Get the string representation of the `Option`. + runnableExamples: + assert $some(42) == "some(42)" + assert $none(int) == "none(int)" + if self.isSome: + when defined(nimLagacyOptionsDollar): + result = "Some(" + else: + result = "some(" + result.addQuoted self.val + result.add ")" + else: + when defined(nimLagacyOptionsDollar): + result = "None[" & name(T) & "]" + else: + result = "none(" & name(T) & ")" + +proc unsafeGet*[T](self: Option[T]): lent T {.inline.}= + ## Returns the value of a `some`. The behavior is undefined for `none`. + ## + ## **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 + result = self.val |