change get/[] to always raise Defect and tryGet to do eh bridge mode (#30)

* also sprinkle mixin randomly across the codebase
This commit is contained in:
Jacek Sieka 2020-04-16 18:23:12 +02:00 committed by GitHub
parent 8528ce28b4
commit ff755bbf75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 52 deletions

View File

@ -112,8 +112,9 @@ type
## ##
## When the error of a `Result` is an `Exception`, or a `toException` helper ## When the error of a `Result` is an `Exception`, or a `toException` helper
## is present for your error type, the "Exception bridge mode" is ## is present for your error type, the "Exception bridge mode" is
## enabled and instead of raising `Defect`, we will raise the given ## enabled and instead of raising `ResultError`, `tryGet` will raise the
## `Exception` on access. ## given `Exception` on access. `[]` and `get` will continue to raise a
## `Defect`.
## ##
## This is an experimental feature that may be removed. ## This is an experimental feature that may be removed.
## ##
@ -274,16 +275,9 @@ func raiseResultDefect(m: string, v: auto) {.noreturn, noinline.} =
when compiles($v): raise (ref ResultDefect)(msg: m & ": " & $v) when compiles($v): raise (ref ResultDefect)(msg: m & ": " & $v)
else: raise (ref ResultDefect)(msg: m) else: raise (ref ResultDefect)(msg: m)
template checkOk(self: Result) = template assertOk(self: Result) =
# TODO This condition is a bit odd in that it raises different exceptions if not self.o:
# depending on the type of E - this is done to support using Result as a raiseResultDefect("Trying to acces value with err Result", self.e)
# bridge type that can transport Exceptions
mixin toException
if not self.isOk:
when E is ref Exception or compiles(toException(self.e)):
raiseResultError(self)
else:
raiseResultDefect("Trying to acces value with err Result", self.e)
template ok*[T, E](R: type Result[T, E], x: auto): R = template ok*[T, E](R: type Result[T, E], x: auto): R =
## Initialize a result with a success and value ## Initialize a result with a success and value
@ -319,39 +313,39 @@ func map*[T, E, A](
## let r = Result[int, cstring).ok(42) ## let r = Result[int, cstring).ok(42)
## assert r.map(proc (v: int): int = $v).get() == "42" ## assert r.map(proc (v: int): int = $v).get() == "42"
## ``` ## ```
if self.isOk: result.ok(f(self.v)) if self.o: result.ok(f(self.v))
else: result.err(self.e) else: result.err(self.e)
func flatMap*[T, E, A]( func flatMap*[T, E, A](
self: Result[T, E], f: proc(x: T): Result[A, E]): Result[A, E] {.inline.} = self: Result[T, E], f: proc(x: T): Result[A, E]): Result[A, E] {.inline.} =
if self.isOk: f(self.v) if self.o: f(self.v)
else: Result[A, E].err(self.e) else: Result[A, E].err(self.e)
func mapErr*[T: not void, E, A]( func mapErr*[T: not void, E, A](
self: Result[T, E], f: proc(x: E): A): Result[T, A] {.inline.} = self: Result[T, E], f: proc(x: E): A): Result[T, A] {.inline.} =
## Transform error using f, or return value ## Transform error using f, or return value
if self.isOk: result.ok(self.v) if self.o: result.ok(self.v)
else: result.err(f(self.e)) else: result.err(f(self.e))
func mapConvert*[T0, E0]( func mapConvert*[T0, E0](
self: Result[T0, E0], T1: type): Result[T1, E0] {.inline.} = self: Result[T0, E0], T1: type): Result[T1, E0] {.inline.} =
## Convert result value to A using an conversion ## Convert result value to A using an conversion
# Would be nice if it was automatic... # Would be nice if it was automatic...
if self.isOk: result.ok(T1(self.v)) if self.o: result.ok(T1(self.v))
else: result.err(self.e) else: result.err(self.e)
func mapCast*[T0, E0]( func mapCast*[T0, E0](
self: Result[T0, E0], T1: type): Result[T1, E0] {.inline.} = self: Result[T0, E0], T1: type): Result[T1, E0] {.inline.} =
## Convert result value to A using a cast ## Convert result value to A using a cast
## Would be nice with nicer syntax... ## Would be nice with nicer syntax...
if self.isOk: result.ok(cast[T1](self.v)) if self.o: result.ok(cast[T1](self.v))
else: result.err(self.e) else: result.err(self.e)
template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1, E] = template `and`*[T0, E, T1](self: Result[T0, E], other: Result[T1, E]): Result[T1, E] =
## Evaluate `other` iff self.isOk, else return error ## Evaluate `other` iff self.isOk, else return error
## fail-fast - will not evaluate other if a is an error ## fail-fast - will not evaluate other if a is an error
let s = self let s = self
if s.isOk: if s.o:
other other
else: else:
type R = type(other) type R = type(other)
@ -361,7 +355,7 @@ template `or`*[T, E](self, other: Result[T, E]): Result[T, E] =
## Evaluate `other` iff not self.isOk, else return self ## Evaluate `other` iff not self.isOk, else return self
## fail-fast - will not evaluate other if a is a value ## fail-fast - will not evaluate other if a is a value
let s = self let s = self
if s.isOk: s if s.o: s
else: other else: other
template catch*(body: typed): Result[type(body), ref CatchableError] = template catch*(body: typed): Result[type(body), ref CatchableError] =
@ -399,9 +393,9 @@ template capture*[E: Exception](T: type, someExceptionExpr: ref E): Result[T, re
ret ret
func `==`*[T0, E0, T1, E1](lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} = func `==`*[T0, E0, T1, E1](lhs: Result[T0, E0], rhs: Result[T1, E1]): bool {.inline.} =
if lhs.isOk != rhs.isOk: if lhs.o != rhs.o:
false false
elif lhs.isOk: elif lhs.o: # and rhs.o implied
lhs.v == rhs.v lhs.v == rhs.v
else: else:
lhs.e == rhs.e lhs.e == rhs.e
@ -410,41 +404,47 @@ func get*[T: not void, E](self: Result[T, E]): T {.inline.} =
## Fetch value of result if set, or raise Defect ## Fetch value of result if set, or raise Defect
## Exception bridge mode: raise given Exception instead ## Exception bridge mode: raise given Exception instead
## See also: Option.get ## See also: Option.get
checkOk(self) assertOk(self)
self.v self.v
func tryGet*[T: not void, E](self: Result[T, E]): T {.inline.} = func tryGet*[T: not void, E](self: Result[T, E]): T {.inline.} =
## Fetch value of result if set, or raise ## Fetch value of result if set, or raise
## When E is an Exception, raise that exception - otherwise, raise a ResultError[E] ## When E is an Exception, raise that exception - otherwise, raise a ResultError[E]
if not self.isOk: self.raiseResultError mixin raiseResultError
if not self.o: self.raiseResultError()
self.v self.v
func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} = func get*[T, E](self: Result[T, E], otherwise: T): T {.inline.} =
## Fetch value of result if set, or return the value `otherwise` ## Fetch value of result if set, or return the value `otherwise`
if self.isErr: otherwise ## See `valueOr` for a template version that avoids evaluating `otherwise`
else: self.v ## unless necessary
if self.o: self.v
else: otherwise
func get*[T, E](self: var Result[T, E]): var T {.inline.} = func get*[T, E](self: var Result[T, E]): var T {.inline.} =
## Fetch value of result if set, or raise Defect ## Fetch value of result if set, or raise Defect
## Exception bridge mode: raise given Exception instead ## Exception bridge mode: raise given Exception instead
## See also: Option.get ## See also: Option.get
checkOk(self) assertOk(self)
self.v self.v
template `[]`*[T, E](self: Result[T, E]): T = template `[]`*[T, E](self: Result[T, E]): T =
## Fetch value of result if set, or raise Defect ## Fetch value of result if set, or raise Defect
## Exception bridge mode: raise given Exception instead ## Exception bridge mode: raise given Exception instead
mixin get
self.get() self.get()
template `[]`*[T, E](self: var Result[T, E]): var T = template `[]`*[T, E](self: var Result[T, E]): var T =
## Fetch value of result if set, or raise Defect ## Fetch value of result if set, or raise Defect
## Exception bridge mode: raise given Exception instead ## Exception bridge mode: raise given Exception instead
mixin get
self.get() self.get()
template unsafeGet*[T, E](self: Result[T, E]): T = template unsafeGet*[T, E](self: Result[T, E]): T =
## Fetch value of result if set, undefined behavior if unset ## Fetch value of result if set, undefined behavior if unset
## See also: Option.unsafeGet ## See also: Option.unsafeGet
assert isOk(self) assert self.o
self.v self.v
func expect*[T: not void, E](self: Result[T, E], m: string): T = func expect*[T: not void, E](self: Result[T, E], m: string): T =
@ -457,37 +457,42 @@ func expect*[T: not void, E](self: Result[T, E], m: string): T =
## # Put here a helpful comment why you think this won't fail ## # Put here a helpful comment why you think this won't fail
## echo r.expect("r was just set to ok(42)") ## echo r.expect("r was just set to ok(42)")
## ``` ## ```
if not self.isOk(): if not self.o:
raiseResultDefect(m, self.error) raiseResultDefect(m, self.e)
self.v self.v
func expect*[T: not void, E](self: var Result[T, E], m: string): var T = func expect*[T: not void, E](self: var Result[T, E], m: string): var T =
if not self.isOk(): if not self.o:
raiseResultDefect(m, self.error) raiseResultDefect(m, self.e)
self.v self.v
func `$`*(self: Result): string = func `$`*(self: Result): string =
## Returns string representation of `self` ## Returns string representation of `self`
if self.isOk: "Ok(" & $self.v & ")" if self.o: "Ok(" & $self.v & ")"
else: "Err(" & $self.e & ")" else: "Err(" & $self.e & ")"
func error*[T, E](self: Result[T, E]): E = func error*[T, E](self: Result[T, E]): E =
## Fetch error of result if set, or raise Defect ## Fetch error of result if set, or raise Defect
if not self.isErr: if self.o:
when T is not void: when T is not void:
raiseResultDefect("Trying to access error when value is set", self.v) raiseResultDefect("Trying to access error when value is set", self.v)
else: else:
raise (ref ResultDefect)(msg: "Trying to access error when value is set") raise (ref ResultDefect)(msg: "Trying to access error when value is set")
self.e self.e
template value*[T, E](self: Result[T, E]): T = self.get() template value*[T, E](self: Result[T, E]): T =
template value*[T, E](self: var Result[T, E]): T = self.get() mixin get
self.get()
template value*[T, E](self: var Result[T, E]): T =
mixin get
self.get()
template valueOr*[T, E](self: Result[T, E], def: T): T = template valueOr*[T, E](self: Result[T, E], def: T): T =
## Fetch value of result if set, or supplied default ## Fetch value of result if set, or supplied default
## default will not be evaluated iff value is set ## default will not be evaluated iff value is set
self.get(def) if self.o: self.v
else: def
# void support # void support
@ -499,10 +504,16 @@ template ok*[E](R: type Result[void, E]): auto =
template ok*[E](self: var Result[void, E]) = template ok*[E](self: var Result[void, E]) =
## Set the result to success and update value ## Set the result to success and update value
## Example: `result.ok(42)` ## Example: `result.ok(42)`
mixin ok
self = (type self).ok() self = (type self).ok()
template ok*(): auto = ok(typeof(result)) template ok*(): auto =
template err*(): auto = err(typeof(result)) mixin ok
ok(typeof(result))
template err*(): auto =
mixin err
err(typeof(result))
# TODO: # TODO:
# Supporting `map` and `get` operations on a `void` result is quite # Supporting `map` and `get` operations on a `void` result is quite
@ -511,55 +522,64 @@ template err*(): auto = err(typeof(result))
func map*[E, A]( func map*[E, A](
self: Result[void, E], f: proc(): A): Result[A, E] {.inline.} = self: Result[void, E], f: proc(): A): Result[A, E] {.inline.} =
## Transform value using f, or return error ## Transform value using f, or return error
if self.isOk: result.ok(f()) if self.o: result.ok(f())
else: result.err(self.e) else: result.err(self.e)
func flatMap*[E, A]( func flatMap*[E, A](
self: Result[void, E], f: proc(): Result[A, E]): Result[A, E] {.inline.} = self: Result[void, E], f: proc(): Result[A, E]): Result[A, E] {.inline.} =
if self.isOk: f(self.v) if self.o: f(self.v)
else: Result[A, E].err(self.e) else: Result[A, E].err(self.e)
func mapErr*[E, A]( func mapErr*[E, A](
self: Result[void, E], f: proc(x: E): A): Result[void, A] {.inline.} = self: Result[void, E], f: proc(x: E): A): Result[void, A] {.inline.} =
## Transform error using f, or return value ## Transform error using f, or return value
if self.isOk: result.ok() if self.o: result.ok()
else: result.err(f(self.e)) else: result.err(f(self.e))
func map*[T, E]( func map*[T, E](
self: Result[T, E], f: proc(x: T)): Result[void, E] {.inline.} = self: Result[T, E], f: proc(x: T)): Result[void, E] {.inline.} =
## Transform value using f, or return error ## Transform value using f, or return error
if self.isOk: f(self.v); result.ok() if self.o: f(self.v); result.ok()
else: result.err(self.e) else: result.err(self.e)
func get*[E](self: Result[void, E]) {.inline.} = func get*[E](self: Result[void, E]) {.inline.} =
## Fetch value of result if set, or raise ## Fetch value of result if set, or raise
## See also: Option.get ## See also: Option.get
checkOk(self) mixin assertOk
assertOk(self)
func tryGet*[E](self: Result[void, E]) {.inline.} = func tryGet*[E](self: Result[void, E]) {.inline.} =
## Fetch value of result if set, or raise a CatchableError ## Fetch value of result if set, or raise a CatchableError
if not self.isOk: self.raiseResultError mixin raiseResultError
if not self.o:
self.raiseResultError()
template `[]`*[E](self: Result[void, E]) = template `[]`*[E](self: Result[void, E]) =
## Fetch value of result if set, or raise ## Fetch value of result if set, or raise
mixin get
self.get() self.get()
template unsafeGet*[E](self: Result[void, E]) = template unsafeGet*[E](self: Result[void, E]) =
## Fetch value of result if set, undefined behavior if unset ## Fetch value of result if set, undefined behavior if unset
## See also: Option.unsafeGet ## See also: Option.unsafeGet
assert not self.isErr assert self.o
func expect*[E](self: Result[void, E], msg: string) = func expect*[E](self: Result[void, E], msg: string) =
if not self.isOk(): if not self.o:
raise (ref ResultDefect)(msg: msg) raise (ref ResultDefect)(msg: msg)
func `$`*[E](self: Result[void, E]): string = func `$`*[E](self: Result[void, E]): string =
## Returns string representation of `self` ## Returns string representation of `self`
if self.isOk: "Ok()" if self.o: "Ok()"
else: "Err(" & $self.e & ")" else: "Err(" & $self.e & ")"
template value*[E](self: Result[void, E]) = self.get() template value*[E](self: Result[void, E]) =
template value*[E](self: var Result[void, E]) = self.get() mixin get
self.get()
template value*[E](self: var Result[void, E]) =
mixin get
self.get()
template `?`*[T, E](self: Result[T, E]): T = template `?`*[T, E](self: Result[T, E]): T =
## Early return - if self is an error, we will return from the current ## Early return - if self is an error, we will return from the current
@ -573,7 +593,7 @@ template `?`*[T, E](self: Result[T, E]): T =
# TODO the v copy is here to prevent multiple evaluations of self - could # TODO the v copy is here to prevent multiple evaluations of self - could
# probably avoid it with some fancy macro magic.. # probably avoid it with some fancy macro magic..
let v = (self) let v = (self)
if v.isErr: if not v.o:
when typeof(result) is typeof(v): when typeof(result) is typeof(v):
return v return v
else: else:

View File

@ -194,7 +194,7 @@ func toException(v: AnEnum): AnException = AnException(v: v)
func testToException(): int = func testToException(): int =
try: try:
var r = Result[int, AnEnum].err(anEnumA) var r = Result[int, AnEnum].err(anEnumA)
r[] r.tryGet
except AnException: except AnException:
42 42