diff --git a/Readme.md b/Readme.md index 79238b0..aaff5f1 100644 --- a/Readme.md +++ b/Readme.md @@ -63,7 +63,7 @@ else: # this is reached, and y is not defined ``` -The `without` statement can be used to place guards that ensure that an optional +The `without` statement can be used to place guards that ensure that an Option contains a value: ```nim diff --git a/questionable/binding.nim b/questionable/binding.nim index bc927bd..3bf3c28 100644 --- a/questionable/binding.nim +++ b/questionable/binding.nim @@ -18,6 +18,10 @@ template bindVar(name, expression): bool = option.isSome macro `=?`*(name, expression): bool = + ## The `=?` operator lets you bind the value inside an Option or Result to a + ## new variable. It can be used inside of a conditional expression, for + ## instance in an `if` statement. + name.expectKind({nnkIdent, nnkVarTy}) if name.kind == nnkIdent: quote do: bindLet(`name`, `expression`) diff --git a/questionable/chaining.nim b/questionable/chaining.nim index 3373a0d..a281f82 100644 --- a/questionable/chaining.nim +++ b/questionable/chaining.nim @@ -8,30 +8,50 @@ func expectSym(node: NimNode) = node.expectKind({nnkSym, nnkOpenSymChoice, nnkClosedSymChoice}) template `.?`*(option: typed, identifier: untyped{nkIdent}): untyped = + ## The `.?` chaining operator is used to safely access fields and call procs + ## on Options or Results. The expression is only evaluated when the preceding + ## Option or Result has a value. + # chain is of shape: option.?identifier when not compiles(typeof(option.unsafeGet.identifier)): {.error: ".? chain cannot return void".} option ->? option.unsafeGet.identifier macro `.?`*(option: typed, infix: untyped{nkInfix}): untyped = + ## The `.?` chaining operator is used to safely access fields and call procs + ## on Options or Results. The expression is only evaluated when the preceding + ## Option or Result has a value. + # chain is of shape: option.?left `operator` right let left = infix[1] infix[1] = quote do: `option`.?`left` infix macro `.?`*(option: typed, bracket: untyped{nkBracketExpr}): untyped = + ## The `.?` chaining operator is used to safely access fields and call procs + ## on Options or Results. The expression is only evaluated when the preceding + ## Option or Result has a value. + # chain is of shape: option.?left[right] let left = bracket[0] bracket[0] = quote do: `option`.?`left` bracket macro `.?`*(option: typed, dot: untyped{nkDotExpr}): untyped = + ## The `.?` chaining operator is used to safely access fields and call procs + ## on Options or Results. The expression is only evaluated when the preceding + ## Option or Result has a value. + # chain is of shape: option.?left.right let left = dot[0] dot[0] = quote do: `option`.?`left` dot macro `.?`*(option: typed, call: untyped{nkCall}): untyped = + ## The `.?` chaining operator is used to safely access fields and call procs + ## on Options or Results. The expression is only evaluated when the preceding + ## Option or Result has a value. + let procedure = call[0] if call.len == 1: # chain is of shape: option.?procedure() @@ -53,6 +73,10 @@ macro `.?`*(option: typed, call: untyped{nkCall}): untyped = quote do: `option` ->? `call` macro `.?`*(option: typed, symbol: untyped): untyped = + ## The `.?` chaining operator is used to safely access fields and call procs + ## on Options or Results. The expression is only evaluated when the preceding + ## Option or Result has a value. + symbol.expectSym() let expression = ident($symbol) quote do: `option`.?`expression` diff --git a/questionable/options.nim b/questionable/options.nim index 0d0d2b8..f0d83ea 100644 --- a/questionable/options.nim +++ b/questionable/options.nim @@ -15,9 +15,15 @@ export indexing export without template `?`*(T: typed): type Option[T] = + ## Use `?` to make a type optional. For example the type `?int` is short for + ## `Option[int]`. + Option[T] template `!`*[T](option: ?T): T = + ## Returns the value of an Option when you're absolutely sure that it + ## contains value. Using `!` on an Option without a value raises a Defect. + option.get template `->?`*[T,U](option: ?T, expression: ?U): ?U = @@ -39,6 +45,9 @@ template `->?`*[T,U,V](options: (?T, ?U), expression: V): ?V = options ->? expression.some template `|?`*[T](option: ?T, fallback: T): T = + ## Use the `|?` operator to supply a fallback value when an Option does not + ## hold a value. + if option.isSome: option.unsafeGet() else: diff --git a/questionable/results.nim b/questionable/results.nim index 20cd9db..4792358 100644 --- a/questionable/results.nim +++ b/questionable/results.nim @@ -18,33 +18,57 @@ export without type ResultFailure* = object of CatchableError template `?!`*(T: typed): type Result[T, ref CatchableError] = + ## 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]`. + Result[T, ref CatchableError] template `!`*[T](value: ?!T): T = + ## Returns the value of a Result when you're absolutely sure that it + ## contains value. Using `!` on a Result without a value raises a Defect. + value.get proc success*[T](value: T): ?!T = + ## Creates a successfull Result containing the value. + ## ok(?!T, value) proc success*: ?!void = + ## Creates a successfull Result without a value. + ok(?!void) proc failure*(T: type, error: ref CatchableError): ?!T = + ## Creates a failed Result containing the error. + err(?!T, error) proc failure*(T: type, message: string): ?!T = + ## Creates a failed Result containing a `ResultFailure` with the specified + ## error message. + T.failure newException(ResultFailure, message) template failure*(error: ref CatchableError): auto = + ## Creates a failed Result containing the error. + err error template failure*(message: string): auto = + ## Creates a failed Result containing the error. + failure newException(ResultFailure, message) proc isSuccess*[T](value: ?!T): bool = + ## Returns true when the Result contains a value. + value.isOk proc isFailure*[T](value: ?!T): bool = + ## Returns true when the Result contains an error. + value.isErr template `->?`*[T,U](value: ?!T, expression: ?!U): ?!U = @@ -68,9 +92,14 @@ template `->?`*[T,U,V](values: (?!T, ?!U), expression: V): ?!V = values ->? expression.success template `|?`*[T,E](value: Result[T,E], fallback: T): T = + ## Use the `|?` operator to supply a fallback value when a Result does not + ## hold a value. + value.valueOr(fallback) proc option*[T,E](value: Result[T,E]): ?T = + ## Converts a Result into an Option. + if value.isOk: try: # workaround for erroneouos exception tracking when T is a closure value.unsafeGet.some diff --git a/questionable/without.nim b/questionable/without.nim index 7851f4d..e2a47a4 100644 --- a/questionable/without.nim +++ b/questionable/without.nim @@ -1,4 +1,6 @@ template without*(expression, body) = + ## Used to place guards that ensure that an Option or Result contains a value. + let ok = expression if not ok: body