plum
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" |