results: collections integration (#179)

This set of helpers allows treating Result and Opt as collections of 0
or 1 item, allowing iterating over them and checking "membership" - such
integration is useful in generic code which can then be generalised to
handle more complex cases - the integration is most useful with Opt.

One design tradeoff here is the "explicitness" of `items` vs `values`
for `Result` - technically error and value are "equal" and therefore we
shouldn't give preference to the value, but there exists a convenience
argument to treat the value as the "default" and therefore define
`items` / `contains` for `Result` as well - this PR chooses the more
conservative and explicit approach - a more liberal version can easily
be added later should motivating examples emerge.
This commit is contained in:
Jacek Sieka 2023-06-07 16:45:53 +02:00 committed by GitHub
parent 000eeb14a3
commit 13e55ed27a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 88 additions and 0 deletions

View File

@ -1076,3 +1076,49 @@ template `?`*[T, E](self: Result[T, E]): auto =
when not(T is void):
v.vResultPrivate
# Collection integration
iterator values*[T, E](self: Result[T, E]): T =
## Iterate over a Result as a 0/1-item collection, returning its value if set
if self.oResultPrivate:
yield self.vResultPrivate
iterator errors*[T, E](self: Result[T, E]): E =
## Iterate over a Result as a 0/1-item collection, returning its error if set
if not self.oResultPrivate:
yield self.eResultPrivate
iterator items*[T](self: Opt[T]): T =
## Iterate over an Opt as a 0/1-item collection, returning its value if set
if self.oResultPrivate:
yield self.vResultPrivate
iterator mvalues*[T, E](self: var Result[T, E]): var T =
if self.oResultPrivate:
yield self.vResultPrivate
iterator merrors*[T, E](self: var Result[T, E]): var E =
if not self.oResultPrivate:
yield self.eResultPrivate
iterator mitems*[T](self: var Opt[T]): var T =
if self.oResultPrivate:
yield self.vResultPrivate
func containsValue*(self: Result, v: auto): bool =
## Return true iff the given result is set to a value that equals `v`
self.oResultPrivate and self.vResultPrivate == v
func containsError*(self: Result, e: auto): bool =
## Return true iff the given result is set to an error that equals `e`
not self.oResultPrivate and self.eResultPrivate == e
func contains*(self: Opt, v: auto): bool =
## Return true iff the given `Opt` is set to a value that equals `v` - can
## also be used in the "infix" `in` form:
##
## ```nim
## assert "value" in Opt.some("value")
## ```
self.oResultPrivate and self.vResultPrivate == v

View File

@ -199,6 +199,37 @@ block:
doAssert rOk.filter(proc(x: int): auto = Result[void, string].err("filter")).error == "filter"
doAssert rErr.filter(proc(x: int): auto = Result[void, string].err("filter")) == rErr
# Collections
block:
var i = 0
for v in rOk.values:
doAssert v == rOk.value()
i += 1
doAssert i == 1
for v in rOk.errors:
raiseAssert "not an error"
doAssert rOk.containsValue(rOk.value())
doAssert not rOk.containsValue(rOk.value() + 1)
doAssert not rOk.containsError("test")
block:
var i = 0
for v in rErr.values:
raiseAssert "not a value"
for v in rErr.errors:
doAssert v == rErr.error()
i += 1
doAssert i == 1
doAssert rErr.containsError(rErr.error())
doAssert not rErr.containsError(rErr.error() & "X")
doAssert not rErr.containsValue(42)
# Exception conversions - toException must not be inside a block
type
AnEnum = enum
@ -381,6 +412,17 @@ block: # Result[T, void] aka `Opt`
doAssert oOk.orErr("error").value() == oOk.get()
doAssert oErr.orErr("error").error() == "error"
# Collections
block:
var i = 0
for v in oOk:
doAssert v == oOk.value()
i += 1
doAssert i == 1
doAssert oOk.value() in oOk
doAssert oOk.value() + 1 notin oOk
block: # `cstring` dangling reference protection
type CSRes = Result[void, cstring]