mirror of
https://github.com/logos-storage/questionable.git
synced 2026-01-02 13:53:11 +00:00
Rename chaining and indexing operators
Chaining operator is now `.?` Indexing operator is now `.?[]` Reason: interoperability with the binding `=?` operator. Chaining and indexing now have lower operator precedence than binding, ensuring that expressions such as `if a =? b.?c` work as expected.
This commit is contained in:
parent
e0d236aaf8
commit
3e92c35a85
16
Readme.md
16
Readme.md
@ -65,29 +65,29 @@ else:
|
||||
|
||||
### Option chaining
|
||||
|
||||
To safely access fields and call procs, you can use the `?.` operator:
|
||||
To safely access fields and call procs, you can use the `.?` operator:
|
||||
|
||||
> Note: before version 0.3.0, the operator was `.?` instead of `?.`
|
||||
> 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 = numbers.?len
|
||||
# amount now holds the integer 3
|
||||
|
||||
numbers = seq[int].none
|
||||
amount = numbers?.len
|
||||
amount = numbers.?len
|
||||
# amount now equals int.none
|
||||
```
|
||||
|
||||
Invocations of the `?.` operator can be chained:
|
||||
Invocations of the `.?` operator can be chained:
|
||||
```nim
|
||||
import sequtils
|
||||
|
||||
numbers = @[1, 1, 2, 2, 2].some
|
||||
amount = numbers?.deduplicate?.len
|
||||
amount = numbers.?deduplicate.?len
|
||||
# amount now holds the integer 2
|
||||
```
|
||||
|
||||
@ -149,7 +149,7 @@ proc fails: ?!seq[int] =
|
||||
|
||||
### Binding, chaining, fallbacks and operators
|
||||
|
||||
Binding with the `=?` operator, chaining with the `?.` operator, fallbacks with
|
||||
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
|
||||
@ -160,7 +160,7 @@ if x =? works():
|
||||
# use x
|
||||
|
||||
# chaining:
|
||||
let amount = works()?.deduplicate?.len
|
||||
let amount = works().?deduplicate.?len
|
||||
|
||||
# fallback values:
|
||||
let value = fails() |? @[]
|
||||
|
||||
@ -1,39 +1,40 @@
|
||||
import std/options
|
||||
import std/macros
|
||||
|
||||
template `?.`*(option: typed, identifier: untyped{nkIdent}): untyped =
|
||||
# chain is of shape: option?.identifier
|
||||
template `.?`*(option: typed, identifier: untyped{nkIdent}): untyped =
|
||||
# chain is of shape: option.?identifier
|
||||
option ->? option.unsafeGet.identifier
|
||||
|
||||
macro `?.`*(option: typed, infix: untyped{nkInfix}): untyped =
|
||||
# chain is of shape: option?.left `operator` right
|
||||
macro `.?`*(option: typed, infix: untyped{nkInfix}): untyped =
|
||||
# chain is of shape: option.?left `operator` right
|
||||
let left = infix[1]
|
||||
infix[1] = quote do: `option`?.`left`
|
||||
infix[1] = quote do: `option`.?`left`
|
||||
infix
|
||||
|
||||
macro `?.`*(option: typed, bracket: untyped{nkBracketExpr}): untyped =
|
||||
# chain is of shape: option?.left[right]
|
||||
macro `.?`*(option: typed, bracket: untyped{nkBracketExpr}): untyped =
|
||||
# chain is of shape: option.?left[right]
|
||||
let left = bracket[0]
|
||||
bracket[0] = quote do: `option`?.`left`
|
||||
bracket[0] = quote do: `option`.?`left`
|
||||
bracket
|
||||
|
||||
macro `?.`*(option: typed, dot: untyped{nkDotExpr}): untyped =
|
||||
# chain is of shape: option?.left.right
|
||||
macro `.?`*(option: typed, dot: untyped{nkDotExpr}): untyped =
|
||||
# chain is of shape: option.?left.right
|
||||
let left = dot[0]
|
||||
dot[0] = quote do: `option`?.`left`
|
||||
dot[0] = quote do: `option`.?`left`
|
||||
dot
|
||||
|
||||
macro `?.`*(option: typed, call: untyped{nkCall}): untyped =
|
||||
macro `.?`*(option: typed, call: untyped{nkCall}): untyped =
|
||||
let procedure = call[0]
|
||||
if call.len == 1:
|
||||
# chain is of shape: option?.procedure()
|
||||
quote do: `option`?.`procedure`
|
||||
# chain is of shape: option.?procedure()
|
||||
quote do: `option`.?`procedure`
|
||||
elif procedure.kind == nnkDotExpr:
|
||||
# chain is of shape: option?.left.right(arguments)
|
||||
# chain is of shape: option.?left.right(arguments)
|
||||
let (left, right) = (procedure[0], procedure[1])
|
||||
call[0] = right
|
||||
call.insert(1, quote do: `option`?.`left`)
|
||||
call.insert(1, quote do: `option`.?`left`)
|
||||
call
|
||||
else:
|
||||
# chain is of shape: option?.procedure(arguments)
|
||||
# chain is of shape: option.?procedure(arguments)
|
||||
call.insert(1, quote do: `option`.unsafeGet)
|
||||
quote do: `option` ->? `call`
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import std/macros
|
||||
|
||||
macro `?`*(expression: typed, brackets: untyped{nkBracket}): untyped =
|
||||
# chain is of shape: expression?[index]
|
||||
macro `.?`*(expression: typed, brackets: untyped{nkBracket}): untyped =
|
||||
# chain is of shape: expression.?[index]
|
||||
let index = brackets[0]
|
||||
quote do:
|
||||
type T = typeof(`expression`[`index`])
|
||||
@ -9,33 +9,3 @@ macro `?`*(expression: typed, brackets: untyped{nkBracket}): untyped =
|
||||
`expression`[`index`].some
|
||||
except KeyError:
|
||||
T.none
|
||||
|
||||
macro `?`*(expression: typed, infix: untyped{nkInfix}): untyped =
|
||||
# chain is of shape: expression?left `operator` right
|
||||
let left = infix[1]
|
||||
infix[1] = quote do: `expression`?`left`
|
||||
infix
|
||||
|
||||
macro `?`*(expression: typed, bracket: untyped{nkBracketExpr}): untyped =
|
||||
# chain is of shape: expression?left[right]
|
||||
let left = bracket[0]
|
||||
bracket[0] = quote do: `expression`?`left`
|
||||
bracket
|
||||
|
||||
macro `?`*(expression: typed, dot: untyped{nkDotExpr}): untyped =
|
||||
# chain is of shape: expression?left.right
|
||||
let left = dot[0]
|
||||
dot[0] = quote do: `expression`?`left`
|
||||
dot
|
||||
|
||||
macro `?`*(expression: typed, call: untyped{nkCall}): untyped =
|
||||
let procedure = call[0]
|
||||
if procedure.kind == nnkDotExpr:
|
||||
# chain is of shape: expression?left.right(arguments)
|
||||
let (left, right) = (procedure[0], procedure[1])
|
||||
call[0] = right
|
||||
call.insert(1, quote do: `expression`?`left`)
|
||||
call
|
||||
else:
|
||||
call.expectKind(nnkBracketExpr)
|
||||
nil
|
||||
|
||||
@ -11,28 +11,28 @@ suite "optionals":
|
||||
check (?string is Option[string])
|
||||
check (?seq[bool] is Option[seq[bool]])
|
||||
|
||||
test "?. can be used for chaining optionals":
|
||||
test ".? can be used for chaining optionals":
|
||||
let a: ?seq[int] = @[41, 42].some
|
||||
let b: ?seq[int] = seq[int].none
|
||||
check a?.len == 2.some
|
||||
check b?.len == int.none
|
||||
check a?.len?.uint8 == 2'u8.some
|
||||
check b?.len?.uint8 == uint8.none
|
||||
check a?.len() == 2.some
|
||||
check b?.len() == int.none
|
||||
check a?.distribute(2)?.len() == 2.some
|
||||
check b?.distribute(2)?.len() == int.none
|
||||
check a.?len == 2.some
|
||||
check b.?len == int.none
|
||||
check a.?len.?uint8 == 2'u8.some
|
||||
check b.?len.?uint8 == uint8.none
|
||||
check a.?len() == 2.some
|
||||
check b.?len() == int.none
|
||||
check a.?distribute(2).?len() == 2.some
|
||||
check b.?distribute(2).?len() == int.none
|
||||
|
||||
test "?. chain can be followed by . calls and operators":
|
||||
test ".? chain can be followed by . calls and operators":
|
||||
let a = @[41, 42].some
|
||||
check a?.len.get == 2
|
||||
check a?.len.get.uint8.uint64 == 2'u64
|
||||
check a?.len.get() == 2
|
||||
check a?.len.get().uint8.uint64 == 2'u64
|
||||
check a?.deduplicate()[0]?.uint8?.uint64 == 41'u64.some
|
||||
check a?.len + 1 == 3.some
|
||||
check a?.deduplicate()[0] + 1 == 42.some
|
||||
check a?.deduplicate.map(x => x) == @[41, 42].some
|
||||
check a.?len.get == 2
|
||||
check a.?len.get.uint8.uint64 == 2'u64
|
||||
check a.?len.get() == 2
|
||||
check a.?len.get().uint8.uint64 == 2'u64
|
||||
check a.?deduplicate()[0].?uint8.?uint64 == 41'u64.some
|
||||
check a.?len + 1 == 3.some
|
||||
check a.?deduplicate()[0] + 1 == 42.some
|
||||
check a.?deduplicate.map(x => x) == @[41, 42].some
|
||||
|
||||
test "[] can be used for indexing optionals":
|
||||
let a: ?seq[int] = @[1, 2, 3].some
|
||||
@ -88,24 +88,11 @@ suite "optionals":
|
||||
if var a =? int.none:
|
||||
fail
|
||||
|
||||
test "?[] can be used for indexing tables without raising KeyError":
|
||||
let table = @{"a": 1, "b": 2}.toTable
|
||||
check table?["a"] == 1.some
|
||||
check table?["c"] == int.none
|
||||
|
||||
test "?[] can be followed by calls, operators and indexing":
|
||||
let table = @{"a": @[41, 42]}.toTable
|
||||
check table?["a"].isSome
|
||||
check table?["a"].isSome()
|
||||
check table?["a"][0] == 41.some
|
||||
check table?["a"]?.len.get == 2
|
||||
check table?["a"]?.len.get.uint8.uint64 == 2'u64
|
||||
check table?["a"]?.len.get() == 2
|
||||
check table?["a"]?.len.get().uint8.uint64 == 2'u64
|
||||
check table?["a"]?.deduplicate()[0]?.uint8?.uint64 == 41'u64.some
|
||||
check table?["a"]?.len + 1 == 3.some
|
||||
check table?["a"]?.deduplicate()[0] + 1 == 42.some
|
||||
check table?["a"]?.deduplicate.map(x => x) == @[41, 42].some
|
||||
test "=? works with .?":
|
||||
if a =? 42.some.?uint8:
|
||||
check a == 42.uint8
|
||||
else:
|
||||
fail
|
||||
|
||||
test "=? evaluates optional expression only once":
|
||||
var count = 0
|
||||
@ -118,6 +105,32 @@ suite "optionals":
|
||||
let b {.used.} = a
|
||||
check count == 1
|
||||
|
||||
test ".?[] can be used for indexing tables without raising KeyError":
|
||||
let table = @{"a": 1, "b": 2}.toTable
|
||||
check table.?["a"] == 1.some
|
||||
check table.?["c"] == int.none
|
||||
|
||||
test ".?[] can be followed by calls, operators and indexing":
|
||||
let table = @{"a": @[41, 42]}.toTable
|
||||
check table.?["a"].isSome
|
||||
check table.?["a"].isSome()
|
||||
check table.?["a"][0] == 41.some
|
||||
check table.?["a"].?len.get == 2
|
||||
check table.?["a"].?len.get.uint8.uint64 == 2'u64
|
||||
check table.?["a"].?len.get() == 2
|
||||
check table.?["a"].?len.get().uint8.uint64 == 2'u64
|
||||
check table.?["a"].?deduplicate()[0].?uint8.?uint64 == 41'u64.some
|
||||
check table.?["a"].?len + 1 == 3.some
|
||||
check table.?["a"].?deduplicate()[0] + 1 == 42.some
|
||||
check table.?["a"].?deduplicate.map(x => x) == @[41, 42].some
|
||||
|
||||
test "=? works with .?[]":
|
||||
let table = @{"a": 42}.toTable
|
||||
if a =? table.?["a"]:
|
||||
check a == 42
|
||||
else:
|
||||
fail
|
||||
|
||||
test "unary operator `-` works for options":
|
||||
check -(-42.some) == 42.some
|
||||
check -(int.none) == int.none
|
||||
@ -176,15 +189,15 @@ suite "optionals":
|
||||
var amount: ?int
|
||||
|
||||
numbers = @[1, 2, 3].some
|
||||
amount = numbers?.len
|
||||
amount = numbers.?len
|
||||
check amount == 3.some
|
||||
|
||||
numbers = seq[int].none
|
||||
amount = numbers?.len
|
||||
amount = numbers.?len
|
||||
check amount == int.none
|
||||
|
||||
numbers = @[1, 1, 2, 2, 2].some
|
||||
amount = numbers?.deduplicate?.len
|
||||
amount = numbers.?deduplicate.?len
|
||||
check amount == 2.some
|
||||
|
||||
# Fallback values
|
||||
|
||||
@ -14,28 +14,28 @@ suite "result":
|
||||
check (?!string is Result[string, ref CatchableError])
|
||||
check (?!seq[bool] is Result[seq[bool], ref CatchableError])
|
||||
|
||||
test "?. can be used for chaining results":
|
||||
test ".? can be used for chaining results":
|
||||
let a: ?!seq[int] = @[41, 42].success
|
||||
let b: ?!seq[int] = seq[int].failure error
|
||||
check a?.len == 2.success
|
||||
check b?.len == int.failure error
|
||||
check a?.len?.uint8 == 2'u8.success
|
||||
check b?.len?.uint8 == uint8.failure error
|
||||
check a?.len() == 2.success
|
||||
check b?.len() == int.failure error
|
||||
check a?.distribute(2)?.len() == 2.success
|
||||
check b?.distribute(2)?.len() == int.failure error
|
||||
check a.?len == 2.success
|
||||
check b.?len == int.failure error
|
||||
check a.?len.?uint8 == 2'u8.success
|
||||
check b.?len.?uint8 == uint8.failure error
|
||||
check a.?len() == 2.success
|
||||
check b.?len() == int.failure error
|
||||
check a.?distribute(2).?len() == 2.success
|
||||
check b.?distribute(2).?len() == int.failure error
|
||||
|
||||
test "?. chain can be followed by . calls and operators":
|
||||
test ".? chain can be followed by . calls and operators":
|
||||
let a = @[41, 42].success
|
||||
check (a?.len.get == 2)
|
||||
check (a?.len.get.uint8.uint64 == 2'u64)
|
||||
check (a?.len.get() == 2)
|
||||
check (a?.len.get().uint8.uint64 == 2'u64)
|
||||
check (a?.deduplicate()[0]?.uint8?.uint64 == 41'u64.success)
|
||||
check (a?.len + 1 == 3.success)
|
||||
check (a?.deduplicate()[0] + 1 == 42.success)
|
||||
check (a?.deduplicate.map(x => x) == @[41, 42].success)
|
||||
check (a.?len.get == 2)
|
||||
check (a.?len.get.uint8.uint64 == 2'u64)
|
||||
check (a.?len.get() == 2)
|
||||
check (a.?len.get().uint8.uint64 == 2'u64)
|
||||
check (a.?deduplicate()[0].?uint8.?uint64 == 41'u64.success)
|
||||
check (a.?len + 1 == 3.success)
|
||||
check (a.?deduplicate()[0] + 1 == 42.success)
|
||||
check (a.?deduplicate.map(x => x) == @[41, 42].success)
|
||||
|
||||
test "[] can be used for indexing optionals":
|
||||
let a: ?!seq[int] = @[1, 2, 3].success
|
||||
@ -91,6 +91,12 @@ suite "result":
|
||||
if var a =? int.failure(error):
|
||||
fail
|
||||
|
||||
test "=? works with .?":
|
||||
if a =? 42.success.?uint8:
|
||||
check a == 42.uint8
|
||||
else:
|
||||
fail
|
||||
|
||||
test "=? evaluates optional expression only once":
|
||||
var count = 0
|
||||
if a =? (inc count; 42.success):
|
||||
@ -159,7 +165,7 @@ suite "result":
|
||||
fail
|
||||
|
||||
# chaining:
|
||||
let amount = works()?.deduplicate?.len
|
||||
let amount = works().?deduplicate.?len
|
||||
check (amount == 2.success)
|
||||
|
||||
# fallback values:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user