plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-std/Option.plum
module std
import std/Result
import std/Str
import std/Bool
import std/Number
# Option[T] represents a value that may or may not be present — Plum's
# counterpart to Rust's `Option`/Go's "zero value or ok bool" idiom.
enum Option[T] =
| Some(T)
| None
fun isSome(self) -> Bool =
match self
Some(_) => True
None => False
fun isNone(self) -> Bool =
match self
Some(_) => False
None => True
# Returns the wrapped value, or traps if `self` is `None`.
fun unwrap(self) -> T =
match self
Some(v) =>
return v
None =>
todo
# Returns the wrapped value, or traps if `self` is `None`. `msg` documents
# the expectation at the call site (matching Rust's `Option::expect`) but
# isn't surfaced anywhere at runtime — there's no host-independent "print
# this and trap" primitive to build that on.
fun expect(self, msg: Str) -> T =
match self
Some(v) =>
return v
None =>
todo
fun unwrapOr(self, default: T) -> T =
match self
Some(v) =>
return v
None =>
return default
# Like `unwrapOr`, but the fallback is computed lazily (only when `self` is
# `None`) via `cb` — useful when producing the default is expensive.
fun unwrapOrElse(self, cb: fn() -> T) -> T =
match self
Some(v) =>
return v
None =>
return cb()
# Keeps `self` only if it's `Some` AND `predicate` holds for its value;
# otherwise returns `None`.
fun filter(self, predicate: fn(T) -> Bool) -> Option[T] =
match self
Some(v) =>
if predicate(v)
return Some(v)
return None
None =>
return None
# Transforms the wrapped value with `cb`, leaving `None` as `None`. `U` is
# `map`'s OWN generic param (the transformed value's type), separate from
# `Option[T]`'s own `T` — resolved per CALL SITE, not when `Option[T]`
# itself is specialized (see `resolveMethodOwnGenerics`).
fun map(self, cb: fn(T) -> U) -> Option[U] =
match self
Some(v) =>
return Some(cb(v))
None =>
return None
# Like `map`, but `cb` itself returns an `Option[U]` (rather than a bare
# `U`) and the result isn't re-wrapped — useful for chaining together
# several fallible steps without nesting (`Option[Option[U]]`). `U` here is
# `andThen`'s OWN generic param, nested inside `cb`'s declared return type
# `Option[U]` rather than being `cb`'s bare return type — resolved from the
# closure's actual inferred return type via `bindGenericArg` in
# `plum-checker/src/monomorphize.rs`'s `resolveMethodOwnGenerics`.
fun andThen(self, cb: fn(T) -> Option[U]) -> Option[U] =
match self
Some(v) =>
return cb(v)
None =>
return None
# Converts to a `Result`, using `err` as the failure value if `self` is
# `None`. `E` is `okOr`'s OWN generic param, inferred directly from `err`'s
# own value (same mechanism as `List.reduce`'s accumulator param).
fun okOr(self, err: E) -> Result[T, E] =
match self
Some(v) =>
return Ok(v)
None =>
return Err(err)
# No `toStr(self) -> Str` here — every method on a generic type gets
# compiled for EVERY concrete specialization that type is ever used at
# ANYWHERE in the whole program, whether or not that particular method is
# actually called for that specialization (there's no lazy/on-demand
# method compilation). `libs/std/list.plum`'s `List[T]` internally uses
# `Option[Node[T]]` for its own `head`/`tail` fields — and `Node` has no
# `toStr` — so an `Option.toStr` calling `.toStr()` on the wrapped value
# would fail to compile for THAT specialization even though nothing ever
# actually calls `.toStr()` on an `Option[Node[Int]]`. Use `toOptionStr`
# below (an explicit stringifier callback, not an implicit `T: ToStr`
# bound) wherever printing an `Option` is needed.
fun toOptionStr(self, valueToStr: fn(T) -> Str) -> Str =
match self
Some(v) =>
return "Some({valueToStr(v)})"
None =>
return "None"
fun makeNoneIntForOptionTest() -> Option[Int] =
return None
test "filter keeps a Some value only when the predicate holds"
assert Some(4).filter(|v| v > 2).isSome()
assert Some(1).filter(|v| v > 2).isNone()
assert makeNoneIntForOptionTest().filter(|v| v > 2).isNone()
test "andThen chains fallible steps without double-wrapping"
half := |n| n % 2 == 0
a := Some(8).andThen(|n| Some(n / 2)).andThen(|n| Some(n / 2))
assert a.isSome()
assert a.unwrap() == 2
assert half(a.unwrap())
b := makeNoneIntForOptionTest().andThen(|n| Some(n / 2))
assert b.isNone()
test "okOr converts to a Result using the given error on None"
assert Some(5).okOr("missing").isOk()
assert Some(5).okOr("missing").unwrap() == 5
assert makeNoneIntForOptionTest().okOr("missing").isErr()
assert makeNoneIntForOptionTest().okOr("missing").unwrapErr() == "missing"