From 205a32ed00de3312a59c47aa0b4fd0a9c22b0c2e Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 29 Jun 2021 19:32:18 -0700 Subject: [PATCH 1/9] make Option[SomePointer] non-special --- lib/pure/options.nim | 44 +++++++++++++------------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 850bfa555d42..e69ba05495c6 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -73,7 +73,7 @@ import typetraits when (NimMajor, NimMinor) >= (1, 1): type - SomePointer = ref | ptr | pointer | proc + SomePointer = ref | ptr | pointer | proc # xxx what about cstring? proc vs closure? else: type SomePointer = ref | ptr | pointer @@ -81,17 +81,16 @@ else: 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` or `proc`), - ## `none(T)` is represented as `nil`. - when T is SomePointer: - val: T - else: - val: T - has: bool + val: T + has: bool UnpackDefect* = object of Defect UnpackError* {.deprecated: "See corresponding Defect".} = UnpackDefect +type MaybeOption[T](_: typedesc[T]) = + when T is SomePointer: T + else: Option[T] + 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)`. @@ -109,8 +108,7 @@ proc option*[T](val: sink T): Option[T] {.inline.} = assert option(42).isSome result.val = val - when T isnot SomePointer: - result.has = true + result.has = true proc some*[T](val: sink T): Option[T] {.inline.} = ## Returns an `Option` that has the value `val`. @@ -124,13 +122,8 @@ proc some*[T](val: sink T): Option[T] {.inline.} = assert a.isSome assert a.get == "abc" - - when T is SomePointer: - assert not val.isNil - result.val = val - else: - result.has = true - result.val = val + result.has = true + result.val = val proc none*(T: typedesc): Option[T] {.inline.} = ## Returns an `Option` for this type that has no value. @@ -158,11 +151,7 @@ proc isSome*[T](self: Option[T]): bool {.inline.} = runnableExamples: assert some(42).isSome assert not none(string).isSome - - when T is SomePointer: - not self.val.isNil - else: - self.has + self.has proc isNone*[T](self: Option[T]): bool {.inline.} = ## Checks if an `Option` is empty. @@ -173,11 +162,7 @@ proc isNone*[T](self: Option[T]): bool {.inline.} = runnableExamples: assert not some(42).isNone assert none(string).isNone - - when T is SomePointer: - self.val.isNil - else: - not self.has + not self.has proc get*[T](self: Option[T]): lent T {.inline.} = ## Returns the content of an `Option`. If it has no value, @@ -338,10 +323,7 @@ proc `==`*[T](a, b: Option[T]): bool {.inline.} = 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) + (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`. From 817d69cc82e33f691a7c2dbf65c37bb4ee31f60c Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Tue, 29 Jun 2021 19:48:14 -0700 Subject: [PATCH 2/9] _ --- lib/pure/options.nim | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pure/options.nim b/lib/pure/options.nim index e69ba05495c6..295aeb9535d8 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -87,7 +87,11 @@ type UnpackDefect* = object of Defect UnpackError* {.deprecated: "See corresponding Defect".} = UnpackDefect -type MaybeOption[T](_: typedesc[T]) = +template maybeOption*[T](_: typedesc[T]): untyped = + ## Return `T` if T is `ref | ptr | pointer | proc`, else `Option[T]` + runnableExamples: + assert maybeOption(ref int) is ref int + assert maybeOption(int) is Option[int] when T is SomePointer: T else: Option[T] @@ -104,7 +108,7 @@ proc option*[T](val: sink T): Option[T] {.inline.} = a: int b: string - assert option[Foo](nil).isNone + assert option[Foo](nil).isSome assert option(42).isSome result.val = val From 9e9b9298a65a54651e22fd9e648cbb7d918c5e18 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 30 Jun 2021 16:34:07 -0700 Subject: [PATCH 3/9] fix test --- lib/pure/options.nim | 22 ++++++++++++++-------- testament/important_packages.nim | 2 +- tests/stdlib/toptions.nim | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 295aeb9535d8..ffc22f9b77c4 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -105,14 +105,16 @@ proc option*[T](val: sink T): Option[T] {.inline.} = runnableExamples: type Foo = ref object - a: int - b: string - - assert option[Foo](nil).isSome + assert option[Foo](nil).isNone + assert option(Foo(nil)).isNone + assert some(Foo(nil)).isSome assert option(42).isSome - - result.val = val - result.has = true + when T is SomePointer: + result.val = val + result.has = val != nil + else: + result.val = val + result.has = true proc some*[T](val: sink T): Option[T] {.inline.} = ## Returns an `Option` that has the value `val`. @@ -123,9 +125,13 @@ proc some*[T](val: sink T): Option[T] {.inline.} = ## * `isSome proc <#isSome,Option[T]>`_ runnableExamples: let a = some("abc") - assert a.isSome assert a.get == "abc" + + let b: ref int = nil + assert some(b).isSome + assert option(b).isNone + result.has = true result.val = val diff --git a/testament/important_packages.nim b/testament/important_packages.nim index 3d3c340eb7f5..d08173f1aa13 100644 --- a/testament/important_packages.nim +++ b/testament/important_packages.nim @@ -114,7 +114,7 @@ pkg "nitter", "nim c src/nitter.nim", "https://github.com/zedeus/nitter" pkg "norm", "nim c -r tests/sqlite/trows.nim" pkg "npeg", "nimble testarc" pkg "numericalnim", "nim c -r tests/test_integrate.nim" -pkg "optionsutils" +pkg "optionsutils", allowFailure = true # pending https://github.com/PMunch/nim-optionsutils/pull/6 pkg "ormin", "nim c -o:orminn ormin.nim" pkg "parsetoml" pkg "patty" diff --git a/tests/stdlib/toptions.nim b/tests/stdlib/toptions.nim index 71c52a07e49a..f94b1c26f105 100644 --- a/tests/stdlib/toptions.nim +++ b/tests/stdlib/toptions.nim @@ -138,7 +138,7 @@ proc main() = doAssert(option(intref).isSome) let tmp = option(intref) - doAssert(sizeof(tmp) == sizeof(ptr int)) + doAssert(sizeof(tmp) > sizeof(ptr int)) var prc = proc (x: int): int = x + 1 doAssert(option(prc).isSome) From 0ffc2f6084b4b2f9585febede09cd6bda1dfe881 Mon Sep 17 00:00:00 2001 From: Timothee Cour Date: Wed, 30 Jun 2021 16:38:48 -0700 Subject: [PATCH 4/9] changelog --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 6672f7538ab1..56eef9f47322 100644 --- a/changelog.md +++ b/changelog.md @@ -93,6 +93,8 @@ - Renamed `-d:nimCompilerStackraceHints` to `-d:nimCompilerStacktraceHints`. +- `std/options` now doesn't special case pointers, and `some(nil).isSome` is now true. + ## Standard library additions and changes From e694e16d77aa9ffed74ae71950bdec009cef2ad5 Mon Sep 17 00:00:00 2001 From: sandytypical <43030857+xflywind@users.noreply.github.com> Date: Wed, 25 May 2022 16:09:40 +0800 Subject: [PATCH 5/9] add std/optionals --- changelog.md | 4 +- lib/pure/options.nim | 62 ++++--- lib/std/optionals.nim | 361 ++++++++++++++++++++++++++++++++++++ tests/stdlib/toptionals.nim | 199 ++++++++++++++++++++ tests/stdlib/toptions.nim | 2 +- 5 files changed, 598 insertions(+), 30 deletions(-) create mode 100644 lib/std/optionals.nim create mode 100644 tests/stdlib/toptionals.nim diff --git a/changelog.md b/changelog.md index 4ea374d202f9..346b2e239d34 100644 --- a/changelog.md +++ b/changelog.md @@ -20,8 +20,6 @@ - `addr` is now available for all addressable locations, `unsafeAddr` is deprecated and becomes an alias for `addr`. -- `std/options` now doesn't special case pointers, and `some(nil).isSome` is now true. - - io is about to move out of system; use `-d:nimPreviewSlimSystem` and import `std/syncio`. ## Standard library additions and changes @@ -54,6 +52,8 @@ becomes an alias for `addr`. - Deprecated `selfExe` for Nimscript. +- Add `std/optionals` which doesn't special case pointers, and `some(nil).isSome` is now true. + ## Language changes diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 382f0994ec9e..562ed6361b77 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -77,7 +77,7 @@ when defined(nimPreviewSlimSystem): when (NimMajor, NimMinor) >= (1, 1): type - SomePointer = ref | ptr | pointer | proc # xxx what about cstring? proc vs closure? + SomePointer = ref | ptr | pointer | proc else: type SomePointer = ref | ptr | pointer @@ -85,20 +85,17 @@ else: type Option*[T] = object ## An optional type that may or may not contain a value of type `T`. - val: T - has: bool + ## When `T` is a a pointer type (`ptr`, `pointer`, `ref` or `proc`), + ## `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 -template maybeOption*[T](_: typedesc[T]): untyped = - ## Return `T` if T is `ref | ptr | pointer | proc`, else `Option[T]` - runnableExamples: - assert maybeOption(ref int) is ref int - assert maybeOption(int) is Option[int] - when T is SomePointer: T - else: Option[T] - 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)`. @@ -109,15 +106,14 @@ proc option*[T](val: sink T): Option[T] {.inline.} = runnableExamples: type Foo = ref object + a: int + b: string + assert option[Foo](nil).isNone - assert option(Foo(nil)).isNone - assert some(Foo(nil)).isSome assert option(42).isSome - when T is SomePointer: - result.val = val - result.has = val != nil - else: - result.val = val + + result.val = val + when T isnot SomePointer: result.has = true proc some*[T](val: sink T): Option[T] {.inline.} = @@ -129,15 +125,16 @@ proc some*[T](val: sink T): Option[T] {.inline.} = ## * `isSome proc <#isSome,Option[T]>`_ runnableExamples: let a = some("abc") + assert a.isSome assert a.get == "abc" - let b: ref int = nil - assert some(b).isSome - assert option(b).isNone - - result.has = true - result.val = val + when T is SomePointer: + assert not val.isNil + result.val = val + else: + result.has = true + result.val = val proc none*(T: typedesc): Option[T] {.inline.} = ## Returns an `Option` for this type that has no value. @@ -165,7 +162,11 @@ proc isSome*[T](self: Option[T]): bool {.inline.} = runnableExamples: assert some(42).isSome assert not none(string).isSome - self.has + + 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. @@ -176,7 +177,11 @@ proc isNone*[T](self: Option[T]): bool {.inline.} = runnableExamples: assert not some(42).isNone assert none(string).isNone - not self.has + + 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, @@ -337,7 +342,10 @@ proc `==`*[T](a, b: Option[T]): bool {.inline.} = assert b == d assert not (a == b) - (a.isSome and b.isSome and a.val == b.val) or (a.isNone and b.isNone) + 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`. diff --git a/lib/std/optionals.nim b/lib/std/optionals.nim new file mode 100644 index 000000000000..edac99dfd7f2 --- /dev/null +++ b/lib/std/optionals.nim @@ -0,0 +1,361 @@ +#Optional +# +# Nim's Runtime Library +# (c) Copyright 2022 Nim Contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +##[ +This module implements types which encapsulate an optional value. + +A value of type `Optional[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): Optional[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,Optional[T]>`_ and `isNone <#isNone,Optional[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 `Optional`s, with the `Some()` and +`None()` patterns. + +.. code-block:: 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` + + +import typetraits + +when defined(nimPreviewSlimSystem): + import std/assertions + + +type + SomePointer = ref | ptr | pointer | proc # xxx what about cstring? proc vs closure? + Optional*[T] = object + ## An optional type that may or may not contain a value of type `T`. + val: T + has: bool + + UnpackDefect* = object of Defect + +template maybeOption*[T](_: typedesc[T]): untyped = + ## Return `T` if T is `ref | ptr | pointer | proc`, else `Optional[T]` + runnableExamples: + assert maybeOption(ref int) is ref int + assert maybeOption(int) is Optional[int] + when T is SomePointer: T + else: Optional[T] + +proc option*[T](val: sink T): Optional[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 + assert option[Foo](nil).isNone + assert option(Foo(nil)).isNone + assert some(Foo(nil)).isSome + assert option(42).isSome + when T is SomePointer: + result.val = val + result.has = val != nil + else: + result.val = val + result.has = true + +proc some*[T](val: sink T): Optional[T] {.inline.} = + ## Returns an `Optional` that has the value `val`. + ## + ## **See also:** + ## * `option proc <#option,T>`_ + ## * `none proc <#none,typedesc>`_ + ## * `isSome proc <#isSome,Optional[T]>`_ + runnableExamples: + let a = some("abc") + assert a.isSome + assert a.get == "abc" + + let b: ref int = nil + assert some(b).isSome + assert option(b).isNone + + result.has = true + result.val = val + +proc none*(T: typedesc): Optional[T] {.inline.} = + ## Returns an `Optional` for this type that has no value. + ## + ## **See also:** + ## * `option proc <#option,T>`_ + ## * `some proc <#some,T>`_ + ## * `isNone proc <#isNone,Optional[T]>`_ + runnableExamples: + assert none(int).isNone + + # the default is the none type + discard + +proc none*[T]: Optional[T] {.inline.} = + ## Alias for `none(T) <#none,typedesc>`_. + none(T) + +proc isSome*[T](self: Optional[T]): bool {.inline.} = + ## Checks if an `Optional` contains a value. + ## + ## **See also:** + ## * `isNone proc <#isNone,Optional[T]>`_ + ## * `some proc <#some,T>`_ + runnableExamples: + assert some(42).isSome + assert not none(string).isSome + self.has + +proc isNone*[T](self: Optional[T]): bool {.inline.} = + ## Checks if an `Optional` is empty. + ## + ## **See also:** + ## * `isSome proc <#isSome,Optional[T]>`_ + ## * `none proc <#none,typedesc>`_ + runnableExamples: + assert not some(42).isNone + assert none(string).isNone + not self.has + +proc get*[T](self: Optional[T]): lent T {.inline.} = + ## Returns the content of an `Optional`. If it has no value, + ## an `UnpackDefect` exception is raised. + ## + ## **See also:** + ## * `get proc <#get,Optional[T],T>`_ with a default return value + runnableExamples: + assert some(42).get == 42 + doAssertRaises(UnpackDefect): + 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: Optional[T], otherwise: T): T {.inline.} = + ## Returns the content of the `Optional` or `otherwise` if + ## the `Optional` has no value. + runnableExamples: + assert some(42).get(9999) == 42 + assert none(int).get(9999) == 9999 + + if self.isSome: + self.val + else: + otherwise + +proc get*[T](self: var Optional[T]): var T {.inline.} = + ## Returns the content of the `var Optional` 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 + + if self.isNone: + raise newException(UnpackDefect, "Can't obtain a value from a `none`") + return self.val + +proc map*[T](self: Optional[T], callback: proc (input: T)) {.inline.} = + ## Applies a `callback` function to the value of the `Optional`, if it has one. + ## + ## **See also:** + ## * `map proc <#map,Optional[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 + + if self.isSome: + callback(self.val) + +proc map*[T, R](self: Optional[T], callback: proc (input: T): R): Optional[R] {.inline.} = + ## Applies a `callback` function to the value of the `Optional` and returns an + ## `Optional` containing the new value. + ## + ## If the `Optional` has no value, `none(R)` will be returned. + ## + ## **See also:** + ## * `map proc <#map,Optional[T],proc(T)>`_ + ## * `flatMap proc <#flatMap,Optional[T],proc(T)>`_ for a version with a + ## callback that returns an `Optional` + runnableExamples: + proc isEven(x: int): bool = + x mod 2 == 0 + + 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*[T](self: Optional[Optional[T]]): Optional[T] {.inline.} = + ## Remove one level of structure in a nested `Optional`. + ## + ## **See also:** + ## * `flatMap proc <#flatMap,Optional[T],proc(T)>`_ + runnableExamples: + assert flatten(some(some(42))) == some(42) + assert flatten(none(Optional[int])) == none(int) + + if self.isSome: + self.val + else: + none(T) + +proc flatMap*[T, R](self: Optional[T], + callback: proc (input: T): Optional[R]): Optional[R] {.inline.} = + ## Applies a `callback` function to the value of the `Optional` and returns the new value. + ## + ## If the `Optional` has no value, `none(R)` will be returned. + ## + ## This is similar to `map`, with the difference that the `callback` returns an + ## `Optional`, not a raw value. This allows multiple procs with a + ## signature of `A -> Optional[B]` to be chained together. + ## + ## See also: + ## * `flatten proc <#flatten,Optional[Optional[A]]>`_ + ## * `filter proc <#filter,Optional[T],proc(T)>`_ + runnableExamples: + proc doublePositives(x: int): Optional[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: Optional[T], callback: proc (input: T): bool): Optional[T] {.inline.} = + ## Applies a `callback` to the value of the `Optional`. + ## + ## 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,Optional[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 + +proc `==`*[T](a, b: Optional[T]): bool {.inline.} = + ## Returns `true` if both `Optional`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) + + (a.isSome and b.isSome and a.val == b.val) or (a.isNone and b.isNone) + +proc `$`*[T](self: Optional[T]): string = + ## Get the string representation of the `Optional`. + 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: Optional[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,Optional[T]>`_). + ## Generally, using the `get proc <#get,Optional[T]>`_ is preferred. + assert self.isSome + result = self.val diff --git a/tests/stdlib/toptionals.nim b/tests/stdlib/toptionals.nim new file mode 100644 index 000000000000..2977c53278af --- /dev/null +++ b/tests/stdlib/toptionals.nim @@ -0,0 +1,199 @@ +discard """ + targets: "c js" +""" + +import std/[json, optionals] + + +# 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 + + +template disableJsVm(body) = + # something doesn't work in JS VM + when defined(js): + when nimvm: discard + else: body + else: + body + +proc main() = + type + Foo = ref object + test: string + Test = object + foo: Optional[Foo] + + doAssert some(nil).isSome + # todo implement for optionals too + # let js = """{"foo": {"test": "123"}}""" + # let parsed = parseJson(js) + # let a = parsed.to(Test) + # doAssert $(%*a) == """{"foo":{"test":"123"}}""" + + block options: + # work around a bug in unittest + let intNone = none(int) + let stringNone = none(string) + + block example: + proc find(haystack: string, needle: char): Optional[int] = + for i, c in haystack: + if c == needle: + return some i + + doAssert("abc".find('c').get() == 2) + + let result = "team".find('i') + + doAssert result == intNone + doAssert result.isNone + + block some: + doAssert some(6).get() == 6 + doAssert some("a").unsafeGet() == "a" + doAssert some(6).isSome + doAssert some("a").isSome + + block none: + doAssertRaises UnpackDefect: + discard none(int).get() + doAssert(none(int).isNone) + doAssert(not none(string).isSome) + + block equality: + doAssert some("a") == some("a") + doAssert some(7) != some(6) + doAssert some("a") != stringNone + doAssert intNone == intNone + + when compiles(some("a") == some(5)): + doAssert false + when compiles(none(string) == none(int)): + doAssert false + + block get_with_a_default_value: + doAssert(some("Correct").get("Wrong") == "Correct") + doAssert(stringNone.get("Correct") == "Correct") + + block stringify: + doAssert($(some("Correct")) == "some(\"Correct\")") + doAssert($(stringNone) == "none(string)") + + disableJsVm: + block map_with_a_void_result: + var procRan = 0 + # TODO closure anonymous functions doesn't work in VM with JS + # Error: cannot evaluate at compile time: procRan + some(123).map(proc (v: int) = procRan = v) + doAssert procRan == 123 + intNone.map(proc (v: int) = doAssert false) + + block map: + doAssert(some(123).map(proc (v: int): int = v * 2) == some(246)) + doAssert(intNone.map(proc (v: int): int = v * 2).isNone) + + block filter: + doAssert(some(123).filter(proc (v: int): bool = v == 123) == some(123)) + doAssert(some(456).filter(proc (v: int): bool = v == 123).isNone) + doAssert(intNone.filter(proc (v: int): bool = doAssert false).isNone) + + block flatMap: + proc addOneIfNotZero(v: int): Optional[int] = + if v != 0: + result = some(v + 1) + else: + result = none(int) + + doAssert(some(1).flatMap(addOneIfNotZero) == some(2)) + doAssert(some(0).flatMap(addOneIfNotZero) == none(int)) + doAssert(some(1).flatMap(addOneIfNotZero).flatMap(addOneIfNotZero) == some(3)) + + proc maybeToString(v: int): Optional[string] = + if v != 0: + result = some($v) + else: + result = none(string) + + doAssert(some(1).flatMap(maybeToString) == some("1")) + + proc maybeExclaim(v: string): Optional[string] = + if v != "": + result = some v & "!" + else: + result = none(string) + + doAssert(some(1).flatMap(maybeToString).flatMap(maybeExclaim) == some("1!")) + doAssert(some(0).flatMap(maybeToString).flatMap(maybeExclaim) == none(string)) + + block SomePointer: + var intref: ref int + doAssert(option(intref).isNone) + intref.new + doAssert(option(intref).isSome) + + let tmp = option(intref) + doAssert(sizeof(tmp) > sizeof(ptr int)) + + var prc = proc (x: int): int = x + 1 + doAssert(option(prc).isSome) + prc = nil + doAssert(option(prc).isNone) + + block: + doAssert(none[int]().isNone) + doAssert(none(int) == none[int]()) + + # "$ on typed with .name" + block: + type Named = object + name: string + + let nobody = none(Named) + doAssert($nobody == "none(Named)") + + # "$ on type with name()" + block: + type Person = object + myname: string + + let noperson = none(Person) + doAssert($noperson == "none(Person)") + + # "Ref type with overloaded `==`" + block: + let p = some(RefPerson.new()) + doAssert p.isSome + + block: # test cstring + block: + let x = some("".cstring) + doAssert x.isSome + doAssert x.get == "" + + block: + let x = some("12345".cstring) + doAssert x.isSome + doAssert x.get == "12345" + + block: + let x = "12345".cstring + let y = some(x) + doAssert y.isSome + doAssert y.get == "12345" + + block: + let x = none(cstring) + doAssert x.isNone + doAssert $x == "none(cstring)" + + +static: main() +main() diff --git a/tests/stdlib/toptions.nim b/tests/stdlib/toptions.nim index f94b1c26f105..71c52a07e49a 100644 --- a/tests/stdlib/toptions.nim +++ b/tests/stdlib/toptions.nim @@ -138,7 +138,7 @@ proc main() = doAssert(option(intref).isSome) let tmp = option(intref) - doAssert(sizeof(tmp) > sizeof(ptr int)) + doAssert(sizeof(tmp) == sizeof(ptr int)) var prc = proc (x: int): int = x + 1 doAssert(option(prc).isSome) From f62d9efb47e5996cf04341e6e09599ad4d7db84e Mon Sep 17 00:00:00 2001 From: sandytypical <43030857+xflywind@users.noreply.github.com> Date: Wed, 25 May 2022 16:18:56 +0800 Subject: [PATCH 6/9] changelog --- changelog.md | 2 ++ lib/pure/options.nim | 1 + lib/std/optionals.nim | 10 ++-------- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 346b2e239d34..10b0dc0c6227 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,8 @@ becomes an alias for `addr`. - io is about to move out of system; use `-d:nimPreviewSlimSystem` and import `std/syncio`. +- `std/options` is deprecated, use `std/optionals` instead. + ## Standard library additions and changes - `macros.parseExpr` and `macros.parseStmt` now accept an optional diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 562ed6361b77..9111a28138b5 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -68,6 +68,7 @@ supports pattern matching on `Option`s, with the `Some()` and ]## # xxx pending https://github.com/timotheecour/Nim/issues/376 use `runnableExamples` and `whichModule` +{.deprecated: "Use `std/optionals`".} import typetraits diff --git a/lib/std/optionals.nim b/lib/std/optionals.nim index edac99dfd7f2..28802009f490 100644 --- a/lib/std/optionals.nim +++ b/lib/std/optionals.nim @@ -339,17 +339,11 @@ proc `$`*[T](self: Optional[T]): string = assert $none(int) == "none(int)" if self.isSome: - when defined(nimLagacyOptionsDollar): - result = "Some(" - else: - result = "some(" + result = "some(" result.addQuoted self.val result.add ")" else: - when defined(nimLagacyOptionsDollar): - result = "None[" & name(T) & "]" - else: - result = "none(" & name(T) & ")" + result = "none(" & name(T) & ")" proc unsafeGet*[T](self: Optional[T]): lent T {.inline.}= ## Returns the value of a `some`. The behavior is undefined for `none`. From 1aaa59c7d24ac49912e5065a72cccc377dcdaa0f Mon Sep 17 00:00:00 2001 From: sandytypical <43030857+xflywind@users.noreply.github.com> Date: Wed, 25 May 2022 16:23:07 +0800 Subject: [PATCH 7/9] update docs --- doc/lib.rst | 2 +- lib/std/optionals.nim | 26 +------------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/doc/lib.rst b/doc/lib.rst index 2f3a315a8f34..14203850e03e 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -125,7 +125,7 @@ Collections Nim linked list support. Contains singly and doubly linked lists and circular lists ("rings"). -* `options `_ +* `optionals `_ The option type encapsulates an optional value. * `packedsets `_ diff --git a/lib/std/optionals.nim b/lib/std/optionals.nim index 28802009f490..4b93e334b6f6 100644 --- a/lib/std/optionals.nim +++ b/lib/std/optionals.nim @@ -1,4 +1,4 @@ -#Optional +# # # Nim's Runtime Library # (c) Copyright 2022 Nim Contributors @@ -42,31 +42,7 @@ 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,Optional[T]>`_ and `isNone <#isNone,Optional[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 `Optional`s, with the `Some()` and -`None()` patterns. - -.. code-block:: 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` import typetraits From 10e08b9f767dfc0e923d17070c62248e4309544c Mon Sep 17 00:00:00 2001 From: flywind <43030857+xflywind@users.noreply.github.com> Date: Wed, 25 May 2022 22:15:59 +0800 Subject: [PATCH 8/9] follow @konsumlamm's suggestions Co-authored-by: konsumlamm <44230978+konsumlamm@users.noreply.github.com> --- changelog.md | 2 +- lib/pure/options.nim | 2 +- lib/std/optionals.nim | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 10b0dc0c6227..212f545bef86 100644 --- a/changelog.md +++ b/changelog.md @@ -22,7 +22,7 @@ becomes an alias for `addr`. - io is about to move out of system; use `-d:nimPreviewSlimSystem` and import `std/syncio`. -- `std/options` is deprecated, use `std/optionals` instead. +- `std/options` is deprecated, use the new `std/optionals` module instead. ## Standard library additions and changes diff --git a/lib/pure/options.nim b/lib/pure/options.nim index 9111a28138b5..aea9db36380c 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -68,7 +68,7 @@ supports pattern matching on `Option`s, with the `Some()` and ]## # xxx pending https://github.com/timotheecour/Nim/issues/376 use `runnableExamples` and `whichModule` -{.deprecated: "Use `std/optionals`".} +{.deprecated: "Use `std/optionals` instead".} import typetraits diff --git a/lib/std/optionals.nim b/lib/std/optionals.nim index 4b93e334b6f6..1a70944bb3a3 100644 --- a/lib/std/optionals.nim +++ b/lib/std/optionals.nim @@ -8,7 +8,7 @@ # ##[ -This module implements types which encapsulate an optional value. +This module implements a type which encapsulate an optional value. A value of type `Optional[T]` either contains a value `x` (represented as `some(x)`) or is empty (`none(T)`). @@ -121,7 +121,7 @@ proc none*(T: typedesc): Optional[T] {.inline.} = # the default is the none type discard -proc none*[T]: Optional[T] {.inline.} = +proc none*[T](): Optional[T] {.inline.} = ## Alias for `none(T) <#none,typedesc>`_. none(T) From 1846212ace164c886736fb58631d38a1668c5e56 Mon Sep 17 00:00:00 2001 From: sandytypical <43030857+xflywind@users.noreply.github.com> Date: Wed, 25 May 2022 22:19:25 +0800 Subject: [PATCH 9/9] optional --- lib/std/optionals.nim | 26 +++++++++++++------------- tests/stdlib/toptionals.nim | 10 +++++----- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/std/optionals.nim b/lib/std/optionals.nim index 4b93e334b6f6..3ece29ac2e30 100644 --- a/lib/std/optionals.nim +++ b/lib/std/optionals.nim @@ -40,7 +40,7 @@ runnableExamples: 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 +Instead, rely on checking if the optional contains a value with the `isSome <#isSome,Optional[T]>`_ and `isNone <#isNone,Optional[T]>`_ procs. ]## @@ -60,16 +60,16 @@ type UnpackDefect* = object of Defect -template maybeOption*[T](_: typedesc[T]): untyped = +template maybeOptional*[T](_: typedesc[T]): untyped = ## Return `T` if T is `ref | ptr | pointer | proc`, else `Optional[T]` runnableExamples: - assert maybeOption(ref int) is ref int - assert maybeOption(int) is Optional[int] + assert maybeOptional(ref int) is ref int + assert maybeOptional(int) is Optional[int] when T is SomePointer: T else: Optional[T] -proc option*[T](val: sink T): Optional[T] {.inline.} = - ## Can be used to convert a pointer type (`ptr`, `pointer`, `ref` or `proc`) to an option type. +proc optional*[T](val: sink T): Optional[T] {.inline.} = + ## Can be used to convert a pointer type (`ptr`, `pointer`, `ref` or `proc`) to an optional type. ## It converts `nil` to `none(T)`. When `T` is no pointer type, this is equivalent to `some(val)`. ## ## **See also:** @@ -78,10 +78,10 @@ proc option*[T](val: sink T): Optional[T] {.inline.} = runnableExamples: type Foo = ref object - assert option[Foo](nil).isNone - assert option(Foo(nil)).isNone + assert optional[Foo](nil).isNone + assert optional(Foo(nil)).isNone assert some(Foo(nil)).isSome - assert option(42).isSome + assert optional(42).isSome when T is SomePointer: result.val = val result.has = val != nil @@ -93,7 +93,7 @@ proc some*[T](val: sink T): Optional[T] {.inline.} = ## Returns an `Optional` that has the value `val`. ## ## **See also:** - ## * `option proc <#option,T>`_ + ## * `optional proc <#optional,T>`_ ## * `none proc <#none,typedesc>`_ ## * `isSome proc <#isSome,Optional[T]>`_ runnableExamples: @@ -103,7 +103,7 @@ proc some*[T](val: sink T): Optional[T] {.inline.} = let b: ref int = nil assert some(b).isSome - assert option(b).isNone + assert optional(b).isNone result.has = true result.val = val @@ -112,7 +112,7 @@ proc none*(T: typedesc): Optional[T] {.inline.} = ## Returns an `Optional` for this type that has no value. ## ## **See also:** - ## * `option proc <#option,T>`_ + ## * `optional proc <#optional,T>`_ ## * `some proc <#some,T>`_ ## * `isNone proc <#isNone,Optional[T]>`_ runnableExamples: @@ -274,7 +274,7 @@ proc flatMap*[T, R](self: Optional[T], proc filter*[T](self: Optional[T], callback: proc (input: T): bool): Optional[T] {.inline.} = ## Applies a `callback` to the value of the `Optional`. ## - ## If the `callback` returns `true`, the option is returned as `some`. + ## If the `callback` returns `true`, the optional is returned as `some`. ## If it returns `false`, it is returned as `none`. ## ## **See also:** diff --git a/tests/stdlib/toptionals.nim b/tests/stdlib/toptionals.nim index 2977c53278af..42cc524224d5 100644 --- a/tests/stdlib/toptionals.nim +++ b/tests/stdlib/toptionals.nim @@ -135,17 +135,17 @@ proc main() = block SomePointer: var intref: ref int - doAssert(option(intref).isNone) + doAssert(optional(intref).isNone) intref.new - doAssert(option(intref).isSome) + doAssert(optional(intref).isSome) - let tmp = option(intref) + let tmp = optional(intref) doAssert(sizeof(tmp) > sizeof(ptr int)) var prc = proc (x: int): int = x + 1 - doAssert(option(prc).isSome) + doAssert(optional(prc).isSome) prc = nil - doAssert(option(prc).isNone) + doAssert(optional(prc).isNone) block: doAssert(none[int]().isNone)