plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-std/Result.plum
module std
import std/Option
import std/Str
import std/Bool
import std/Number
# Result[T, E] represents either success (`Ok`, carrying a `T`) or failure
# (`Err`, carrying an `E`) — used throughout std for fallible operations
# (`Bool.parse`, `Int.fromStr`, `Float.fromStr`).
#
# `andThen` (which would need `cb`'s return type's `T` to be found NESTED
# inside another `Result[U, E]`, not `cb`'s own bare return type) is
# intentionally not implemented here — same method-level-generics gap
# documented on `Option.andThen` in `libs/std/option.plum`. `map`/`mapErr`
# don't have that problem (`cb`'s return type IS directly the new generic
# param, same shape as `List.map`), so those ARE implemented below.
enum Result[T, E] =
| Ok(T)
| Err(E)
fun isOk(self) -> Bool =
match self
Ok(_) => True
Err(_) => False
fun isErr(self) -> Bool =
match self
Ok(_) => False
Err(_) => True
# Returns the wrapped success value, or traps if `self` is `Err`.
fun unwrap(self) -> T =
match self
Ok(v) =>
return v
Err(_) =>
todo
# Returns the wrapped error value, or traps if `self` is `Ok`.
fun unwrapErr(self) -> E =
match self
Ok(_) =>
todo
Err(e) =>
return e
# Returns the wrapped success value, or traps if `self` is `Err`. `msg`
# documents the expectation at the call site (matching Rust's
# `Result::expect`) but isn't surfaced anywhere at runtime — see the
# identical note on `Option.expect`.
fun expect(self, msg: Str) -> T =
match self
Ok(v) =>
return v
Err(_) =>
todo
fun expectErr(self, msg: Str) -> E =
match self
Ok(_) =>
todo
Err(e) =>
return e
fun unwrapOr(self, default: T) -> T =
match self
Ok(v) =>
return v
Err(_) =>
return default
# Like `unwrapOr`, but the fallback is computed lazily from the error via
# `cb` (only when `self` is `Err`).
fun unwrapOrElse(self, cb: fn(E) -> T) -> T =
match self
Ok(v) =>
return v
Err(e) =>
return cb(e)
# Discards the error, keeping only the success value (if any).
fun ok(self) -> Option[T] =
match self
Ok(v) =>
return Some(v)
Err(_) =>
return None
# Discards the success value, keeping only the error (if any).
fun err(self) -> Option[E] =
match self
Ok(_) =>
return None
Err(e) =>
return Some(e)
# Transforms the success value with `cb`, leaving `Err` untouched. `U` is
# `map`'s OWN generic param, separate from `Result[T, E]`'s own `T`/`E` —
# resolved per CALL SITE, same mechanism as `List.map`/`Option.map`.
fun map(self, cb: fn(T) -> U) -> Result[U, E] =
match self
Ok(v) =>
return Ok(cb(v))
Err(e) =>
return Err(e)
# Transforms the error value with `cb`, leaving `Ok` untouched. `F` is
# `mapErr`'s OWN generic param, separate from `Result[T, E]`'s own `T`/`E`.
fun mapErr(self, cb: fn(E) -> F) -> Result[T, F] =
match self
Ok(v) =>
return Ok(v)
Err(e) =>
return Err(cb(e))
# No implicit `toStr(self) -> Str` — see the identical note on
# `Option.toOptionStr` in `libs/std/option.plum`: a method that assumes
# `T`/`E: ToStr` gets compiled for EVERY specialization of `Result`
# anywhere in the program (there's no lazy/on-demand method compilation),
# so an implicit bound like this can break an unrelated specialization
# whose payload doesn't support it. Explicit stringifier callbacks instead.
fun toResultStr(self, okToStr: fn(T) -> Str, errToStr: fn(E) -> Str) -> Str =
match self
Ok(v) =>
return "Ok({okToStr(v)})"
Err(e) =>
return "Err({errToStr(e)})"
fun makeOkForResultTest() -> Result[Int, Str] =
return Ok(5)
fun makeErrForResultTest() -> Result[Int, Str] =
return Err("bad")
test "map transforms an Ok value and leaves Err untouched"
assert makeOkForResultTest().map(|v| v * 2).unwrap() == 10
assert makeErrForResultTest().map(|v| v * 2).isErr() == True
test "mapErr transforms an Err value and leaves Ok untouched"
assert makeErrForResultTest().mapErr(|e| e + "!").unwrapErr() == "bad!"
assert makeOkForResultTest().mapErr(|e| e + "!").unwrap() == 5