plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


plum-std/Option.plum
fa31ba4 1
module std
fa31ba4 2
b7071c9 3
import std/Result
b7071c9 4
import std/Str
73b5e55 5
import std/Bool
ca5fd6f 6
import std/Number
2ec05c8 7
a271f34 8
# Option[T] represents a value that may or may not be present — Plum's
a271f34 9
# counterpart to Rust's `Option`/Go's "zero value or ok bool" idiom.
287b97c 10
enum Option[T] =
287b97c 11
  | Some(T)
fa31ba4 12
  | None
a271f34 13
a271f34 14
  fun isSome(self) -> Bool =
a271f34 15
    match self
3a2119e 16
      Some(_) => True
3a2119e 17
      None => False
a271f34 18
a271f34 19
  fun isNone(self) -> Bool =
a271f34 20
    match self
3a2119e 21
      Some(_) => False
3a2119e 22
      None => True
a271f34 23
a271f34 24
  # Returns the wrapped value, or traps if `self` is `None`.
a271f34 25
  fun unwrap(self) -> T =
a271f34 26
    match self
a271f34 27
      Some(v) =>
a271f34 28
        return v
a271f34 29
      None =>
a271f34 30
        todo
a271f34 31
a271f34 32
  # Returns the wrapped value, or traps if `self` is `None`. `msg` documents
a271f34 33
  # the expectation at the call site (matching Rust's `Option::expect`) but
a271f34 34
  # isn't surfaced anywhere at runtime — there's no host-independent "print
a271f34 35
  # this and trap" primitive to build that on.
a271f34 36
  fun expect(self, msg: Str) -> T =
a271f34 37
    match self
a271f34 38
      Some(v) =>
a271f34 39
        return v
a271f34 40
      None =>
a271f34 41
        todo
a271f34 42
a271f34 43
  fun unwrapOr(self, default: T) -> T =
a271f34 44
    match self
a271f34 45
      Some(v) =>
a271f34 46
        return v
a271f34 47
      None =>
a271f34 48
        return default
a271f34 49
a271f34 50
  # Like `unwrapOr`, but the fallback is computed lazily (only when `self` is
a271f34 51
  # `None`) via `cb` — useful when producing the default is expensive.
a271f34 52
  fun unwrapOrElse(self, cb: fn() -> T) -> T =
a271f34 53
    match self
a271f34 54
      Some(v) =>
a271f34 55
        return v
a271f34 56
      None =>
a271f34 57
        return cb()
a271f34 58
a271f34 59
  # Keeps `self` only if it's `Some` AND `predicate` holds for its value;
a271f34 60
  # otherwise returns `None`.
a271f34 61
  fun filter(self, predicate: fn(T) -> Bool) -> Option[T] =
a271f34 62
    match self
a271f34 63
      Some(v) =>
a271f34 64
        if predicate(v)
a271f34 65
          return Some(v)
a271f34 66
        return None
a271f34 67
      None =>
a271f34 68
        return None
a271f34 69
a271f34 70
  # Transforms the wrapped value with `cb`, leaving `None` as `None`. `U` is
a271f34 71
  # `map`'s OWN generic param (the transformed value's type), separate from
a271f34 72
  # `Option[T]`'s own `T` — resolved per CALL SITE, not when `Option[T]`
a271f34 73
  # itself is specialized (see `resolveMethodOwnGenerics`).
a271f34 74
  fun map(self, cb: fn(T) -> U) -> Option[U] =
a271f34 75
    match self
a271f34 76
      Some(v) =>
a271f34 77
        return Some(cb(v))
a271f34 78
      None =>
a271f34 79
        return None
a271f34 80
2ec05c8 81
  # Like `map`, but `cb` itself returns an `Option[U]` (rather than a bare
2ec05c8 82
  # `U`) and the result isn't re-wrapped — useful for chaining together
2ec05c8 83
  # several fallible steps without nesting (`Option[Option[U]]`). `U` here is
2ec05c8 84
  # `andThen`'s OWN generic param, nested inside `cb`'s declared return type
2ec05c8 85
  # `Option[U]` rather than being `cb`'s bare return type — resolved from the
2ec05c8 86
  # closure's actual inferred return type via `bindGenericArg` in
2ec05c8 87
  # `plum-checker/src/monomorphize.rs`'s `resolveMethodOwnGenerics`.
2ec05c8 88
  fun andThen(self, cb: fn(T) -> Option[U]) -> Option[U] =
2ec05c8 89
    match self
2ec05c8 90
      Some(v) =>
2ec05c8 91
        return cb(v)
2ec05c8 92
      None =>
2ec05c8 93
        return None
2ec05c8 94
2ec05c8 95
  # Converts to a `Result`, using `err` as the failure value if `self` is
2ec05c8 96
  # `None`. `E` is `okOr`'s OWN generic param, inferred directly from `err`'s
2ec05c8 97
  # own value (same mechanism as `List.reduce`'s accumulator param).
2ec05c8 98
  fun okOr(self, err: E) -> Result[T, E] =
2ec05c8 99
    match self
2ec05c8 100
      Some(v) =>
2ec05c8 101
        return Ok(v)
2ec05c8 102
      None =>
2ec05c8 103
        return Err(err)
2ec05c8 104
a271f34 105
  # No `toStr(self) -> Str` here — every method on a generic type gets
a271f34 106
  # compiled for EVERY concrete specialization that type is ever used at
a271f34 107
  # ANYWHERE in the whole program, whether or not that particular method is
a271f34 108
  # actually called for that specialization (there's no lazy/on-demand
a271f34 109
  # method compilation). `libs/std/list.plum`'s `List[T]` internally uses
a271f34 110
  # `Option[Node[T]]` for its own `head`/`tail` fields — and `Node` has no
a271f34 111
  # `toStr` — so an `Option.toStr` calling `.toStr()` on the wrapped value
a271f34 112
  # would fail to compile for THAT specialization even though nothing ever
a271f34 113
  # actually calls `.toStr()` on an `Option[Node[Int]]`. Use `toOptionStr`
bf629a2 114
  # below (an explicit stringifier callback, not an implicit `T: ToStr`
a271f34 115
  # bound) wherever printing an `Option` is needed.
a271f34 116
  fun toOptionStr(self, valueToStr: fn(T) -> Str) -> Str =
a271f34 117
    match self
a271f34 118
      Some(v) =>
a271f34 119
        return "Some({valueToStr(v)})"
a271f34 120
      None =>
a271f34 121
        return "None"
2ec05c8 122
2ec05c8 123
fun makeNoneIntForOptionTest() -> Option[Int] =
2ec05c8 124
  return None
2ec05c8 125
2ec05c8 126
test "filter keeps a Some value only when the predicate holds"
a9a0147 127
  assert Some(4).filter(|v| v > 2).isSome()
a9a0147 128
  assert Some(1).filter(|v| v > 2).isNone()
a9a0147 129
  assert makeNoneIntForOptionTest().filter(|v| v > 2).isNone()
2ec05c8 130
2ec05c8 131
test "andThen chains fallible steps without double-wrapping"
2ec05c8 132
  half := |n| n % 2 == 0
2ec05c8 133
  a := Some(8).andThen(|n| Some(n / 2)).andThen(|n| Some(n / 2))
a9a0147 134
  assert a.isSome()
a9a0147 135
  assert a.unwrap() == 2
a9a0147 136
  assert half(a.unwrap())
2ec05c8 137
  b := makeNoneIntForOptionTest().andThen(|n| Some(n / 2))
a9a0147 138
  assert b.isNone()
2ec05c8 139
2ec05c8 140
test "okOr converts to a Result using the given error on None"
a9a0147 141
  assert Some(5).okOr("missing").isOk()
a9a0147 142
  assert Some(5).okOr("missing").unwrap() == 5
a9a0147 143
  assert makeNoneIntForOptionTest().okOr("missing").isErr()
a9a0147 144
  assert makeNoneIntForOptionTest().okOr("missing").unwrapErr() == "missing"