235 lines
4.8 KiB
Markdown
235 lines
4.8 KiB
Markdown
Questionable 🤔
|
|
==============
|
|
|
|
[Option][1] and [Result][2] 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][3] package manager to add `questionable` to an existing
|
|
project. Add the following to its .nimble file:
|
|
|
|
```nim
|
|
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][2] package, or the [stew][4] package:
|
|
|
|
```nim
|
|
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]`][1].
|
|
|
|
```nim
|
|
import questionable
|
|
|
|
var x: ?int
|
|
```
|
|
|
|
Assigning values is done using the `some` and `none` procs from the standard library:
|
|
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
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 `.?`
|
|
|
|
```nim
|
|
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:
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
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.
|
|
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
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]`.
|
|
|
|
```nim
|
|
proc example: ?!int =
|
|
# either return an integer or an error
|
|
```
|
|
|
|
Results can be made using the `success` and `failure` procs:
|
|
|
|
```nim
|
|
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:
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
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`:
|
|
|
|
```nim
|
|
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:
|
|
|
|
```nim
|
|
let converted = works().option # equals @[1, 1, 2, 2, 2].some
|
|
```
|
|
|
|
[1]: https://nim-lang.org/docs/options.html
|
|
[2]: https://github.com/arnetheduck/nim-result
|
|
[3]: https://github.com/nim-lang/nimble
|
|
[4]: https://github.com/status-im/nim-stew
|