From 32ebcfa8ff7cafc34ba60c101e6fe6f59b5be52d Mon Sep 17 00:00:00 2001 From: Oleh Prypin Date: Sat, 11 Apr 2015 04:49:40 +0300 Subject: [PATCH 1/6] Implement optionals module --- lib/pure/optionals.nim | 262 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 lib/pure/optionals.nim diff --git a/lib/pure/optionals.nim b/lib/pure/optionals.nim new file mode 100644 index 0000000000..ef0b6e1089 --- /dev/null +++ b/lib/pure/optionals.nim @@ -0,0 +1,262 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2015 Nim Contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## :Author: Oleh Prypin +## +## Abstract +## ======== +## +## This module implements types which encapsulate an optional value. +## +## A value of type ``?T`` (``Maybe[T]``) either contains a value `x` +## (represented as ``just(x)``) or is empty (``nothing(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 optionals +## +## proc find(haystack: string, needle: char): ?int = +## for i, c in haystack: +## if c == needle: +## return just i +## return nothing(int) # This line is actually optional, +## # because the default is empty +## +## The ``?`` operator (template) is a shortcut for ``Maybe[T]``. +## +## .. code-block:: nim +## +## try: +## assert("abc".find('c')[] == 2) # Immediately extract the value +## except FieldError: # If there is no value +## assert false # This will not be reached, because the value is present +## +## The ``[]`` operator demonstrated above returns the underlying value, or +## raises ``FieldError`` if there is no value. There is another option for +## obtaining the value: ``val``, but you must only use it when you are +## absolutely sure the value is present (e.g. after checking ``has``). If you do +## not care about the tiny overhead that ``[]`` causes, you should simply never +## use ``val``. +## +## How to deal with an absence of a value: +## +## .. code-block:: nim +## +## let result = "team".find('i') +## +## # Nothing was found, so the result is `nothing`. +## assert(result == nothing(int)) +## # It has no value: +## assert(result.has == false) +## # A different way to write it: +## assert(not result) +## +## try: +## echo result[] +## assert(false) # This will not be reached +## except FieldError: # Because an exception is raised +## discard +## +## Now let's try out the extraction template. It returns whether a value +## is present and injects the value into a variable. It is meant to be used in +## a conditional. +## +## .. code-block:: nim +## +## if pos ?= "nim".find('i'): +## assert(pos is int) # This is a normal integer, no tricks. +## echo "Match found at position ", pos +## else: +## assert(false) # This will not be reached +## +## Or maybe you want to get the behavior of the standard library's ``find``, +## which returns `-1` if nothing was found. +## +## .. code-block:: nim +## +## assert(("team".find('i') or -1) == -1) +## assert(("nim".find('i') or -1) == 1) + +import typetraits + + +type + Maybe*[T] = object + ## An optional type that stores its value and state separately in a boolean. + val: T + has: bool + + +template `?`*(T: typedesc): typedesc = + ## ``?T`` is equivalent to ``Maybe[T]``. + Maybe[T] + + +proc just*[T](val: T): Maybe[T] = + ## Returns a ``Maybe`` that has this value. + result.has = true + result.val = val + +proc nothing*(T: typedesc): Maybe[T] = + ## Returns a ``Maybe`` for this type that has no value. + result.has = false + + +proc has*(maybe: Maybe): bool = + ## Returns ``true`` if `maybe` isn't `nothing`. + maybe.has + +converter toBool*(maybe: Maybe): bool = + ## Same as ``has``. Allows to use a ``Maybe`` in boolean context. + maybe.has + + +proc unsafeVal*[T](maybe: Maybe[T]): T = + ## Returns the value of a `just`. Behavior is undefined for `nothing`. + assert maybe.has, "nothing has no val" + maybe.val + +proc `[]`*[T](maybe: Maybe[T]): T = + ## Returns the value of `maybe`. Raises ``FieldError`` if it is `nothing`. + if not maybe: + raise newException(FieldError, "Can't obtain a value from a `nothing`") + maybe.val + + +template `or`*[T](maybe: Maybe[T], default: T): T = + ## Returns the value of `maybe`, or `default` if it is `nothing`. + if maybe: maybe.val + else: default + +template `or`*[T](a, b: Maybe[T]): Maybe[T] = + ## Returns `a` if it is `just`, otherwise `b`. + if a: a + else: b + +template `?=`*(into: expr, maybe: Maybe): bool = + ## Returns ``true`` if `maybe` isn't `nothing`. + ## + ## Injects a variable with the name specified by the argument `into` + ## with the value of `maybe`, or its type's default value if it is `nothing`. + ## + ## .. code-block:: nim + ## + ## proc message(): ?string = + ## just "Hello" + ## + ## if m ?= message(): + ## echo m + var into {.inject.}: type(maybe.val) + if maybe: + into = maybe.val + maybe + + +proc `==`*(a, b: Maybe): bool = + ## Returns ``true`` if both ``Maybe`` are `nothing`, + ## or if they have equal values + (a.has and b.has and a.val == b.val) or (not a.has and not b.has) + +proc `$`[T](maybe: Maybe[T]): string = + ## Converts to string: `"just(value)"` or `"nothing(type)"` + if maybe.has: + "just(" & $maybe.val & ")" + else: + "nothing(" & T.name & ")" + + +when isMainModule: + template expect(E: expr, body: stmt) = + try: + body + assert false, E.type.name & " not raised" + except E: + discard + + + block: # example + proc find(haystack: string, needle: char): ?int = + for i, c in haystack: + if c == needle: + return just i + + assert("abc".find('c')[] == 2) + + let result = "team".find('i') + + assert result == nothing(int) + assert result.has == false + + if pos ?= "nim".find('i'): + assert pos is int + assert pos == 1 + else: + assert false + + assert(("team".find('i') or -1) == -1) + assert(("nim".find('i') or -1) == 1) + + block: # just + assert just(6)[] == 6 + assert just("a").unsafeVal == "a" + assert just(6).has + assert just("a") + + block: # nothing + expect FieldError: + discard nothing(int)[] + assert(not nothing(int).has) + assert(not nothing(string)) + + block: # equality + assert just("a") == just("a") + assert just(7) != just(6) + assert just("a") != nothing(string) + assert nothing(int) == nothing(int) + + when compiles(just("a") == just(5)): + assert false + when compiles(nothing(string) == nothing(int)): + assert false + + block: # stringification + assert "just(7)" == $just(7) + assert "nothing(int)" == $nothing(int) + + block: # or + assert just(1) or just(2) == just(1) + assert nothing(string) or just("a") == just("a") + assert nothing(int) or nothing(int) == nothing(int) + assert just(5) or 2 == 2 + assert nothing(string) or "a" == "a" + + when compiles(just(1) or "2"): + assert false + when compiles(nothing(int) or just("a")): + assert false + + block: # extraction template + if a ?= just(5): + assert a == 5 + else: + assert false + + if b ?= nothing(string): + assert false From a66dcd9d945dbc48745a6f32b097150bb4574145 Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Tue, 19 May 2015 16:52:24 -0400 Subject: [PATCH 2/6] Simplify optionals module --- lib/pure/optionals.nim | 216 ++++++++++++----------------------------- 1 file changed, 60 insertions(+), 156 deletions(-) diff --git a/lib/pure/optionals.nim b/lib/pure/optionals.nim index ef0b6e1089..fb76c90374 100644 --- a/lib/pure/optionals.nim +++ b/lib/pure/optionals.nim @@ -14,11 +14,11 @@ ## ## This module implements types which encapsulate an optional value. ## -## A value of type ``?T`` (``Maybe[T]``) either contains a value `x` -## (represented as ``just(x)``) or is empty (``nothing(T)``). +## 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 +## 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. ## ## @@ -32,28 +32,26 @@ ## ## import optionals ## -## proc find(haystack: string, needle: char): ?int = +## proc find(haystack: string, needle: char): Option[int] = ## for i, c in haystack: ## if c == needle: -## return just i -## return nothing(int) # This line is actually optional, -## # because the default is empty -## -## The ``?`` operator (template) is a shortcut for ``Maybe[T]``. +## return some(i) +## return none(int) # This line is actually optional, +## # because the default is empty ## ## .. code-block:: nim ## ## try: -## assert("abc".find('c')[] == 2) # Immediately extract the value +## assert("abc".find('c').get() == 2) # Immediately extract the value ## except FieldError: # If there is no value ## assert false # This will not be reached, because the value is present ## -## The ``[]`` operator demonstrated above returns the underlying value, or +## The ``get`` operation demonstrated above returns the underlying value, or ## raises ``FieldError`` if there is no value. There is another option for -## obtaining the value: ``val``, but you must only use it when you are -## absolutely sure the value is present (e.g. after checking ``has``). If you do -## not care about the tiny overhead that ``[]`` causes, you should simply never -## use ``val``. +## 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: ## @@ -61,126 +59,62 @@ ## ## let result = "team".find('i') ## -## # Nothing was found, so the result is `nothing`. -## assert(result == nothing(int)) +## # Nothing was found, so the result is `none`. +## assert(result == none(int)) ## # It has no value: -## assert(result.has == false) -## # A different way to write it: -## assert(not result) +## assert(result.isNone) ## ## try: -## echo result[] +## echo result.get() ## assert(false) # This will not be reached ## except FieldError: # Because an exception is raised ## discard -## -## Now let's try out the extraction template. It returns whether a value -## is present and injects the value into a variable. It is meant to be used in -## a conditional. -## -## .. code-block:: nim -## -## if pos ?= "nim".find('i'): -## assert(pos is int) # This is a normal integer, no tricks. -## echo "Match found at position ", pos -## else: -## assert(false) # This will not be reached -## -## Or maybe you want to get the behavior of the standard library's ``find``, -## which returns `-1` if nothing was found. -## -## .. code-block:: nim -## -## assert(("team".find('i') or -1) == -1) -## assert(("nim".find('i') or -1) == 1) import typetraits type - Maybe*[T] = object + Option*[T] = object ## An optional type that stores its value and state separately in a boolean. val: T has: bool -template `?`*(T: typedesc): typedesc = - ## ``?T`` is equivalent to ``Maybe[T]``. - Maybe[T] - - -proc just*[T](val: T): Maybe[T] = - ## Returns a ``Maybe`` that has this value. +proc some*[T](val: T): Option[T] = + ## Returns a ``Option`` that has this value. result.has = true result.val = val -proc nothing*(T: typedesc): Maybe[T] = - ## Returns a ``Maybe`` for this type that has no value. +proc none*(T: typedesc): Option[T] = + ## Returns a ``Option`` for this type that has no value. result.has = false -proc has*(maybe: Maybe): bool = - ## Returns ``true`` if `maybe` isn't `nothing`. - maybe.has +proc isSome*[T](self: Option[T]): bool = + self.has -converter toBool*(maybe: Maybe): bool = - ## Same as ``has``. Allows to use a ``Maybe`` in boolean context. - maybe.has +proc isNone*[T](self: Option[T]): bool = + not self.has -proc unsafeVal*[T](maybe: Maybe[T]): T = - ## Returns the value of a `just`. Behavior is undefined for `nothing`. - assert maybe.has, "nothing has no val" - maybe.val +proc unsafeGet*[T](self: Option[T]): T = + ## Returns the value of a `just`. Behavior is undefined for `none`. + assert self.isSome + self.val -proc `[]`*[T](maybe: Maybe[T]): T = - ## Returns the value of `maybe`. Raises ``FieldError`` if it is `nothing`. - if not maybe: - raise newException(FieldError, "Can't obtain a value from a `nothing`") - maybe.val +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 newException(FieldError, "Can't obtain a value from a `none`") + self.val -template `or`*[T](maybe: Maybe[T], default: T): T = - ## Returns the value of `maybe`, or `default` if it is `nothing`. - if maybe: maybe.val - else: default - -template `or`*[T](a, b: Maybe[T]): Maybe[T] = - ## Returns `a` if it is `just`, otherwise `b`. - if a: a - else: b - -template `?=`*(into: expr, maybe: Maybe): bool = - ## Returns ``true`` if `maybe` isn't `nothing`. - ## - ## Injects a variable with the name specified by the argument `into` - ## with the value of `maybe`, or its type's default value if it is `nothing`. - ## - ## .. code-block:: nim - ## - ## proc message(): ?string = - ## just "Hello" - ## - ## if m ?= message(): - ## echo m - var into {.inject.}: type(maybe.val) - if maybe: - into = maybe.val - maybe - - -proc `==`*(a, b: Maybe): bool = - ## Returns ``true`` if both ``Maybe`` are `nothing`, +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) -proc `$`[T](maybe: Maybe[T]): string = - ## Converts to string: `"just(value)"` or `"nothing(type)"` - if maybe.has: - "just(" & $maybe.val & ")" - else: - "nothing(" & T.name & ")" - when isMainModule: template expect(E: expr, body: stmt) = @@ -192,71 +126,41 @@ when isMainModule: block: # example - proc find(haystack: string, needle: char): ?int = + proc find(haystack: string, needle: char): Option[int] = for i, c in haystack: if c == needle: - return just i + return some i - assert("abc".find('c')[] == 2) + assert("abc".find('c').get() == 2) let result = "team".find('i') - assert result == nothing(int) + assert result == none(int) assert result.has == false - if pos ?= "nim".find('i'): - assert pos is int - assert pos == 1 - else: - assert false + block: # some + assert some(6).get() == 6 + assert some("a").unsafeGet() == "a" + assert some(6).isSome + assert some("a").isSome - assert(("team".find('i') or -1) == -1) - assert(("nim".find('i') or -1) == 1) - - block: # just - assert just(6)[] == 6 - assert just("a").unsafeVal == "a" - assert just(6).has - assert just("a") - - block: # nothing + block: # none expect FieldError: - discard nothing(int)[] - assert(not nothing(int).has) - assert(not nothing(string)) + discard none(int).get() + assert(none(int).isNone) + assert(not none(string).isSome) block: # equality - assert just("a") == just("a") - assert just(7) != just(6) - assert just("a") != nothing(string) - assert nothing(int) == nothing(int) + assert some("a") == some("a") + assert some(7) != some(6) + assert some("a") != none(string) + assert none(int) == none(int) - when compiles(just("a") == just(5)): + when compiles(some("a") == some(5)): assert false - when compiles(nothing(string) == nothing(int)): + when compiles(none(string) == none(int)): assert false block: # stringification - assert "just(7)" == $just(7) - assert "nothing(int)" == $nothing(int) - - block: # or - assert just(1) or just(2) == just(1) - assert nothing(string) or just("a") == just("a") - assert nothing(int) or nothing(int) == nothing(int) - assert just(5) or 2 == 2 - assert nothing(string) or "a" == "a" - - when compiles(just(1) or "2"): - assert false - when compiles(nothing(int) or just("a")): - assert false - - block: # extraction template - if a ?= just(5): - assert a == 5 - else: - assert false - - if b ?= nothing(string): - assert false + assert "some(7)" == $some(7) + assert "none(int)" == $none(int) From ae0c8573f2ac1840373948b086e1f4d4c6a4180f Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Tue, 19 May 2015 16:58:39 -0400 Subject: [PATCH 3/6] Simplify optionals tests --- lib/pure/optionals.nim | 71 ++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/lib/pure/optionals.nim b/lib/pure/optionals.nim index fb76c90374..30b2505ad6 100644 --- a/lib/pure/optionals.nim +++ b/lib/pure/optionals.nim @@ -117,50 +117,45 @@ proc `==`*(a, b: Option): bool = when isMainModule: - template expect(E: expr, body: stmt) = - try: - body - assert false, E.type.name & " not raised" - except E: - discard + import unittest + suite "optionals": + # work around a bug in unittest + let intNone = none(int) + let stringNone = none(string) - block: # example - proc find(haystack: string, needle: char): Option[int] = - for i, c in haystack: - if c == needle: - return some i + test "example": + proc find(haystack: string, needle: char): Option[int] = + for i, c in haystack: + if c == needle: + return some i - assert("abc".find('c').get() == 2) + check("abc".find('c').get() == 2) - let result = "team".find('i') + let result = "team".find('i') - assert result == none(int) - assert result.has == false + check result == intNone + check result.isNone - block: # some - assert some(6).get() == 6 - assert some("a").unsafeGet() == "a" - assert some(6).isSome - assert some("a").isSome + test "some": + check some(6).get() == 6 + check some("a").unsafeGet() == "a" + check some(6).isSome + check some("a").isSome - block: # none - expect FieldError: - discard none(int).get() - assert(none(int).isNone) - assert(not none(string).isSome) + test "none": + expect FieldError: + discard none(int).get() + check(none(int).isNone) + check(not none(string).isSome) - block: # equality - assert some("a") == some("a") - assert some(7) != some(6) - assert some("a") != none(string) - assert none(int) == none(int) + 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)): - assert false - when compiles(none(string) == none(int)): - assert false - - block: # stringification - assert "some(7)" == $some(7) - assert "none(int)" == $none(int) + when compiles(some("a") == some(5)): + check false + when compiles(none(string) == none(int)): + check false From caa730127bbe1ba359eb4640ea52cd4e1e5cf540 Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Tue, 19 May 2015 17:47:54 -0400 Subject: [PATCH 4/6] Use custom exception for option unpack --- lib/pure/optionals.nim | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/pure/optionals.nim b/lib/pure/optionals.nim index 30b2505ad6..6fda5996e5 100644 --- a/lib/pure/optionals.nim +++ b/lib/pure/optionals.nim @@ -43,11 +43,11 @@ ## ## try: ## assert("abc".find('c').get() == 2) # Immediately extract the value -## except FieldError: # If there is no 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 ``FieldError`` if there is no value. There is another option for +## 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 @@ -67,7 +67,7 @@ ## try: ## echo result.get() ## assert(false) # This will not be reached -## except FieldError: # Because an exception is raised +## except UnpackError: # Because an exception is raised ## discard import typetraits @@ -78,6 +78,7 @@ type ## An optional type that stores its value and state separately in a boolean. val: T has: bool + UnpackError* = ref object of ValueError proc some*[T](val: T): Option[T] = @@ -106,7 +107,7 @@ 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 newException(FieldError, "Can't obtain a value from a `none`") + raise UnpackError(msg : "Can't obtain a value from a `none`") self.val @@ -144,7 +145,7 @@ when isMainModule: check some("a").isSome test "none": - expect FieldError: + expect UnpackError: discard none(int).get() check(none(int).isNone) check(not none(string).isSome) From d3ab60c8310328b2838b42f0998515ddbb8a6ac4 Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Sun, 24 May 2015 18:20:37 -0400 Subject: [PATCH 5/6] Remove Oleah Prypin as author Done on request, see https://github.com/Araq/Nim/pull/2762#issuecomment-105071496 --- lib/pure/optionals.nim | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pure/optionals.nim b/lib/pure/optionals.nim index 6fda5996e5..d0d832f59d 100644 --- a/lib/pure/optionals.nim +++ b/lib/pure/optionals.nim @@ -7,8 +7,6 @@ # distribution, for details about the copyright. # -## :Author: Oleh Prypin -## ## Abstract ## ======== ## From f9e95b29878e548c8adc4ac15d5880fa26843515 Mon Sep 17 00:00:00 2001 From: Flaviu Tamas Date: Tue, 26 May 2015 09:21:15 -0400 Subject: [PATCH 6/6] Amend optionals docstring --- lib/pure/optionals.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/optionals.nim b/lib/pure/optionals.nim index d0d832f59d..ef01e12601 100644 --- a/lib/pure/optionals.nim +++ b/lib/pure/optionals.nim @@ -97,7 +97,7 @@ proc isNone*[T](self: Option[T]): bool = proc unsafeGet*[T](self: Option[T]): T = - ## Returns the value of a `just`. Behavior is undefined for `none`. + ## Returns the value of a ``some``. Behavior is undefined for ``none``. assert self.isSome self.val @@ -110,7 +110,7 @@ proc get*[T](self: Option[T]): T = proc `==`*(a, b: Option): bool = - ## Returns ``true`` if both ``Option``s are `none`, + ## 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)