plum

#treesitter#compiler#wasm

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