Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
bring the language in line with the standard library (e.g. ``parseOct``).
- The dot style for import paths (e.g ``import path.to.module`` instead of
``import path/to/module``) has been deprecated.
- The ``options`` module has been split in two

#### Breaking changes in the standard library

Expand Down Expand Up @@ -75,6 +76,7 @@
- ``net.sendTo`` no longer returns an int and now raises an ``OSError``.
- `threadpool`'s `await` and derivatives have been renamed to `blockUntil`
to avoid confusions with `await` from the `async` macro.
- ``options`` has been split into ``options`` and ``optionsutils``


#### Breaking changes in the compiler
Expand Down Expand Up @@ -111,6 +113,7 @@
- Added a simple interpreting event parser template ``eventParser`` to the ``pegs`` module.
- Added ``macros.copyLineInfo`` to copy lineInfo from other node.
- Added ``system.ashr`` an arithmetic right shift for integers.
- Added ``allSome`` macro to ``optionsutils`` as a safer pattern for option unpacking.

### Library changes

Expand Down
109 changes: 29 additions & 80 deletions lib/pure/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -146,42 +146,12 @@ proc get*[T](self: var Option[T]): var T =
raise UnpackError(msg: "Can't obtain a value from a `none`")
return self.val

proc map*[T](self: Option[T], callback: proc (input: T)) =
## Applies a callback to the value in this Option
if self.isSome:
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.isSome:
some[R]( callback(self.val) )
else:
none(R)

proc flatten*[A](self: Option[Option[A]]): Option[A] =
## Remove one level of structure in a nested Option.
if self.isSome:
self.val
else:
none(A)

proc flatMap*[A, B](self: Option[A], callback: proc (input: A): Option[B]): Option[B] =
## 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. 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]`` (including A = B) to be chained together.
map(self, callback).flatten()

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.isSome and not callback(self.val):
none(T)
else:
self
template either*(self, otherwise: untyped): untyped =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the name

## Similar in function to ``get``, but if ``otherwise`` is a procedure it will
## not be evaluated if ``self`` is a ``some``. This means that ``otherwise``
## can have side effects.
let opt = self # In case self is a procedure call returning an option
if opt.isSome: opt.val else: otherwise

proc `==`*(a, b: Option): bool =
## Returns ``true`` if both ``Option``s are ``none``,
Expand Down Expand Up @@ -251,50 +221,6 @@ when isMainModule:
test "$":
check($(some("Correct")) == "Some(\"Correct\")")
check($(stringNone) == "None[string]")

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)

test "map":
check(some(123).map(proc (v: int): int = v * 2) == some(246))
check(intNone.map(proc (v: int): int = v * 2).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)

test "flatMap":
proc addOneIfNotZero(v: int): Option[int] =
if v != 0:
result = some(v + 1)
else:
result = none(int)

check(some(1).flatMap(addOneIfNotZero) == some(2))
check(some(0).flatMap(addOneIfNotZero) == none(int))
check(some(1).flatMap(addOneIfNotZero).flatMap(addOneIfNotZero) == some(3))

proc maybeToString(v: int): Option[string] =
if v != 0:
result = some($v)
else:
result = none(string)

check(some(1).flatMap(maybeToString) == some("1"))

proc maybeExclaim(v: string): Option[string] =
if v != "":
result = some v & "!"
else:
result = none(string)

check(some(1).flatMap(maybeToString).flatMap(maybeExclaim) == some("1!"))
check(some(0).flatMap(maybeToString).flatMap(maybeExclaim) == none(string))

test "SomePointer":
var intref: ref int
check(option(intref).isNone)
Expand All @@ -321,3 +247,26 @@ when isMainModule:

let noperson = none(Person)
check($noperson == "None[Person]")

test "either":
check(either(some("Correct"), "Wrong") == "Correct")
check(either(stringNone, "Correct") == "Correct")

test "either without side effect":
var evaluated = 0
proc dummySome(): Option[string] =
evaluated += 1
return some("dummy")
proc dummyStr(): string =
evaluated += 1
return "dummy"
# Check that dummyStr isn't called when we have an option
check(either(some("Correct"), dummyStr()) == "Correct")
check evaluated == 0
# Check that dummyStr is called when we don't have an option
check(either(stringNone, dummyStr()) == "dummy")
check evaluated == 1
evaluated = 0
# Check that dummySome is only called once when used as the some value
check(either(dummySome(), "Wrong") == "dummy")
check evaluated == 1
Loading