Awesome
Questionable 🤔
Option and Result are two powerful abstractions that can be used instead of raising errors. They can be a bit unwieldy though. This library is an attempt at making their use a bit more elegant.
Installation
Use the Nimble package manager to add questionable
to an existing
project. Add the following to its .nimble file:
requires "questionable >= 0.10.15 & < 0.11.0"
If you want to make use of Result types, then you also have to add either the result package, or the stew package:
requires "results" # either this
requires "stew" # or this
Options
You can use ?
to make a type optional. For example, the type ?int
is just
short for Option[int]
.
import questionable
var x: ?int
Assigning values is done using the some
and none
procs from the standard library:
x = 42.some # Option x now holds the value 42
x = int.none # Option x no longer holds a value
Option binding
The =?
operator lets you bind the value inside an Option to a new variable. It
can be used inside of a conditional expression, for instance in an if
statement:
x = 42.some
if y =? x:
# y equals 42 here
else:
# this is never reached
x = int.none
if y =? x:
# this is never reached
else:
# this is reached, and y is not defined
The without
statement can be used to place guards that ensure that an Option
contains a value:
proc someProc(option: ?int) =
without value =? option:
# option did not contain a value
return
# use value
Option chaining
To safely access fields and call procs, you can use the .?
operator:
Note: in versions 0.3.x and 0.4.x, the operator was
?.
instead of.?
var numbers: ?seq[int]
var amount: ?int
numbers = @[1, 2, 3].some
amount = numbers.?len
# amount now holds the integer 3
numbers = seq[int].none
amount = numbers.?len
# amount now equals int.none
Invocations of the .?
operator can be chained:
import sequtils
numbers = @[1, 1, 2, 2, 2].some
amount = numbers.?deduplicate.?len
# amount now holds the integer 2
Fallback values
Use the |?
operator to supply a fallback value when the Option does not hold
a value:
x = int.none
let z = x |? 3
# z equals 3
Obtaining value with !
The !
operator returns the value of an Option when you're absolutely sure that
it contains a value.
x = 42.some
let dare = !x # dare equals 42
x = int.none
let crash = !x # raises a Defect
Operators
The operators []
, -
, +
, @
, *
, /
, div
, mod
, shl
, shr
, &
,
<=
, <
, >=
, >
are all lifted, so they can be used directly on Options:
numbers = @[1, 2, 3].some
x = 39.some
let indexed = numbers[0] # equals 1.some
let sum = x + 3 # equals 42.some
Results
Support for Result
is considered experimental. If you want to use them you
have to explicitly import the questionable/results
module:
import questionable/results
You can use ?!
make a Result type. These Result types either hold a value or
an error. For example the type ?!int
is short for Result[int, ref CatchableError]
.
proc example: ?!int =
# either return an integer or an error
Results can be made using the success
and failure
procs:
proc works: ?!seq[int] =
# always returns a Result holding a sequence
success @[1, 1, 2, 2, 2]
proc fails: ?!seq[int] =
# always returns a Result holding an error
failure "something went wrong"
Binding, chaining, fallbacks and operators
Binding with the =?
operator, chaining with the .?
operator, fallbacks with
the |?
operator, and all the other operators that work with Options also work
for Results:
import sequtils
# binding:
if x =? works():
# use x
# chaining:
let amount = works().?deduplicate.?len
# fallback values:
let value = fails() |? @[]
# lifted operators:
let sum = works()[3] + 40
Without statement
The without
statement can also be used with Results. It provides access to any
errors that may arise:
proc someProc(r: ?!int) =
without value =? r, error:
# use `error` to get the error from r
return
# use value
Catching errors
When you want to use Results, but need to call a proc that may raise an
error, you can use catch
:
import strutils
let x = parseInt("42").catch # equals 42.success
let y = parseInt("XX").catch # equals int.failure(..)
Conversion to Option
Any Result can be converted to an Option:
let converted = works().option # equals @[1, 1, 2, 2, 2].some