summary refs log tree commit diff stats
path: root/lib
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2015-05-26 15:33:08 +0200
committerAndreas Rumpf <rumpf_a@web.de>2015-05-26 15:33:08 +0200
commit1ebff2ef83f21568642cd8bb20b1009001d1f8e0 (patch)
tree19cadc83b6644af079aacaf48da116dda5fbc589 /lib
parenta932f63a030392928831464f3c7e64e42ce5a763 (diff)
parentf9e95b29878e548c8adc4ac15d5880fa26843515 (diff)
downloadNim-1ebff2ef83f21568642cd8bb20b1009001d1f8e0.tar.gz
Merge pull request #2762 from flaviut/optionals
Optionals
Diffstat (limited to 'lib')
-rw-r--r--lib/pure/optionals.nim160
1 files changed, 160 insertions, 0 deletions
diff --git a/lib/pure/optionals.nim b/lib/pure/optionals.nim
new file mode 100644
index 000000000..ef01e1260
--- /dev/null
+++ b/lib/pure/optionals.nim
@@ -0,0 +1,160 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Nim Contributors
+#
+#    See the file "copying.txt", included in this
+#    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 optionals
+##
+##   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
+
+
+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
+
+
+proc some*[T](val: T): Option[T] =
+  ## Returns a ``Option`` that has this value.
+  result.has = true
+  result.val = val
+
+proc none*(T: typedesc): Option[T] =
+  ## Returns a ``Option`` for this type that has no value.
+  result.has = false
+
+
+proc isSome*[T](self: Option[T]): bool =
+  self.has
+
+proc isNone*[T](self: Option[T]): bool =
+  not self.has
+
+
+proc unsafeGet*[T](self: Option[T]): T =
+  ## Returns the value of a ``some``. Behavior is undefined for ``none``.
+  assert self.isSome
+  self.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 UnpackError(msg : "Can't obtain a value from a `none`")
+  self.val
+
+
+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 isMainModule:
+  import unittest
+
+  suite "optionals":
+    # 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 UnpackError:
+        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