fork download
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Nim Contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9.  
  10. ## :Author: Oleh Prypin
  11. ##
  12. ## Abstract
  13. ## ========
  14. ##
  15. ## This module implements types which encapsulate an optional value.
  16. ##
  17. ## A value of type ``?T`` (``Maybe[T]``) either contains a value `x`
  18. ## (represented as ``just(x)``) or is empty (``nothing(T)``).
  19. ##
  20. ## This can be useful when you have a value that can be present or not.
  21. ## The absence of a value is often represented by ``nil``, but it is not always
  22. ## available, nor is it always a good solution.
  23. ##
  24. ##
  25. ## Tutorial
  26. ## ========
  27. ##
  28. ## Let's start with an example: a procedure that finds the index of a character
  29. ## in a string.
  30. ##
  31. ## .. code-block:: nim
  32. ##
  33. ## import optionals
  34. ##
  35. ## proc find(haystack: string, needle: char): ?int =
  36. ## for i, c in haystack:
  37. ## if c == needle:
  38. ## return just i
  39. ## return nothing(int) # This line is actually optional,
  40. ## # because the default is empty
  41. ##
  42. ## The ``?`` operator (template) is a shortcut for ``Maybe[T]``.
  43. ##
  44. ## .. code-block:: nim
  45. ##
  46. ## try:
  47. ## assert("abc".find('c')[] == 2) # Immediately extract the value
  48. ## except FieldError: # If there is no value
  49. ## assert false # This will not be reached, because the value is present
  50. ##
  51. ## The ``[]`` operator demonstrated above returns the underlying value, or
  52. ## raises ``FieldError`` if there is no value. There is another option for
  53. ## obtaining the value: ``val``, but you must only use it when you are
  54. ## absolutely sure the value is present (e.g. after checking ``has``). If you do
  55. ## not care about the tiny overhead that ``[]`` causes, you should simply never
  56. ## use ``val``.
  57. ##
  58. ## How to deal with an absence of a value:
  59. ##
  60. ## .. code-block:: nim
  61. ##
  62. ## let result = "team".find('i')
  63. ##
  64. ## # Nothing was found, so the result is `nothing`.
  65. ## assert(result == nothing(int))
  66. ## # It has no value:
  67. ## assert(result.has == false)
  68. ## # A different way to write it:
  69. ## assert(not result)
  70. ##
  71. ## try:
  72. ## echo result[]
  73. ## assert(false) # This will not be reached
  74. ## except FieldError: # Because an exception is raised
  75. ## discard
  76. ##
  77. ## Now let's try out the extraction template. It returns whether a value
  78. ## is present and injects the value into a variable. It is meant to be used in
  79. ## a conditional.
  80. ##
  81. ## .. code-block:: nim
  82. ##
  83. ## if pos ?= "nim".find('i'):
  84. ## assert(pos is int) # This is a normal integer, no tricks.
  85. ## echo "Match found at position ", pos
  86. ## else:
  87. ## assert(false) # This will not be reached
  88. ##
  89. ## Or maybe you want to get the behavior of the standard library's ``find``,
  90. ## which returns `-1` if nothing was found.
  91. ##
  92. ## .. code-block:: nim
  93. ##
  94. ## assert(("team".find('i') or -1) == -1)
  95. ## assert(("nim".find('i') or -1) == 1)
  96.  
  97. type
  98. Maybe*[T] = object
  99. ## An optional type that stores its value and state separately in a boolean.
  100. val: T
  101. has: bool
  102.  
  103.  
  104. template `?`*(T: typedesc): typedesc =
  105. ## ``?T`` is equivalent to ``Maybe[T]``.
  106. Maybe[T]
  107.  
  108.  
  109. proc just*[T](val: T): Maybe[T] =
  110. ## Returns a ``Maybe`` that has this value.
  111. result.has = true
  112. result.val = val
  113.  
  114. proc nothing*(T: typedesc): Maybe[T] =
  115. ## Returns a ``Maybe`` for this type that has no value.
  116. result.has = false
  117.  
  118.  
  119. proc has*(maybe: Maybe): bool =
  120. ## Returns ``true`` if `maybe` isn't `nothing`.
  121. maybe.has
  122.  
  123. converter toBool*(maybe: Maybe): bool =
  124. ## Same as ``has``. Allows to use a ``Maybe`` in boolean context.
  125. maybe.has
  126.  
  127.  
  128. proc unsafeVal*[T](maybe: Maybe[T]): T =
  129. ## Returns the value of a `just`. Behavior is undefined for `nothing`.
  130. assert maybe.has, "nothing has no val"
  131. maybe.val
  132.  
  133. proc `[]`*[T](maybe: Maybe[T]): T =
  134. ## Returns the value of `maybe`. Raises ``FieldError`` if it is `nothing`.
  135. if not maybe:
  136. raise newException(FieldError, "Can't obtain a value from a `nothing`")
  137. maybe.val
  138.  
  139.  
  140. template `or`*[T](maybe: Maybe[T], default: T): T =
  141. ## Returns the value of `maybe`, or `default` if it is `nothing`.
  142. if maybe: maybe.val
  143. else: default
  144.  
  145. template `or`*[T](a, b: Maybe[T]): Maybe[T] =
  146. ## Returns `a` if it is `just`, otherwise `b`.
  147. if a: a
  148. else: b
  149.  
  150. template `?=`*(into: expr, maybe: Maybe): bool =
  151. ## Returns ``true`` if `maybe` isn't `nothing`.
  152. ##
  153. ## Injects a variable with the name specified by the argument `into`
  154. ## with the value of `maybe`, or its type's default value if it is `nothing`.
  155. ##
  156. ## .. code-block:: nim
  157. ##
  158. ## proc message(): ?string =
  159. ## just "Hello"
  160. ##
  161. ## if m ?= message():
  162. ## echo m
  163. var into {.inject.}: type(maybe.val)
  164. if maybe:
  165. into = maybe.val
  166. maybe
  167.  
  168.  
  169. proc `==`*(a, b: Maybe): bool =
  170. ## Returns ``true`` if both ``Maybe`` are `nothing`,
  171. ## or if they have equal values
  172. (a.has and b.has and a.val == b.val) or (not a.has and not b.has)
  173.  
  174. proc `$`[T](maybe: Maybe[T]): string =
  175. ## Converts to string: `"just(value)"` or `"nothing(type)"`
  176. if maybe.has:
  177. "just(" & $maybe.val & ")"
  178. else:
  179. "nothing(" & T.name & ")"
  180.  
  181.  
  182. when isMainModule:
  183. template expect(E: expr, body: stmt) =
  184. try:
  185. body
  186. assert false, E.type.name & " not raised"
  187. except E:
  188. discard
  189.  
  190.  
  191. block: # example
  192. proc find(haystack: string, needle: char): ?int =
  193. for i, c in haystack:
  194. if c == needle:
  195. return just i
  196.  
  197. assert("abc".find('c')[] == 2)
  198.  
  199. let result = "team".find('i')
  200.  
  201. assert result == nothing(int)
  202. assert result.has == false
  203.  
  204. if pos ?= "nim".find('i'):
  205. assert pos is int
  206. assert pos == 1
  207. else:
  208. assert false
  209.  
  210. assert(("team".find('i') or -1) == -1)
  211. assert(("nim".find('i') or -1) == 1)
  212.  
  213. block: # just
  214. assert just(6)[] == 6
  215. assert just("a").unsafeVal == "a"
  216. assert just(6).has
  217. assert just("a")
  218.  
  219. block: # nothing
  220. expect FieldError:
  221. discard nothing(int)[]
  222. assert(not nothing(int).has)
  223. assert(not nothing(string))
  224.  
  225. block: # equality
  226. assert just("a") == just("a")
  227. assert just(7) != just(6)
  228. assert just("a") != nothing(string)
  229. assert nothing(int) == nothing(int)
  230.  
  231. when compiles(just("a") == just(5)):
  232. assert false
  233. when compiles(nothing(string) == nothing(int)):
  234. assert false
  235.  
  236. block: # stringification
  237. assert "just(7)" == $just(7)
  238. assert "nothing(int)" == $nothing(int)
  239.  
  240. block: # or
  241. assert just(1) or just(2) == just(1)
  242. assert nothing(string) or just("a") == just("a")
  243. assert nothing(int) or nothing(int) == nothing(int)
  244. assert just(5) or 2 == 2
  245. assert nothing(string) or "a" == "a"
  246.  
  247. when compiles(just(1) or "2"):
  248. assert false
  249. when compiles(nothing(int) or just("a")):
  250. assert false
  251.  
  252. block: # extraction template
  253. if a ?= just(5):
  254. assert a == 5
  255. else:
  256. assert false
  257.  
  258. if b ?= nothing(string):
  259. assert false
  260.  
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
prog.nim(114, 35) Error: undeclared identifier: 'T'
stdout
Standard output is empty