plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
9cc75c2
— Peter John
2026-11-01T22:42:05+05:30
docs: merge duplicated README passages
README.md
CHANGED
|
@@ -217,7 +217,7 @@ From tightest to loosest binding:
|
|
|
217
217
|
| | `!` | boolean not |
|
|
218
218
|
| | `&&` | |
|
|
219
219
|
| | `\|\|` | |
|
|
220
|
-
| | `?:` | optional default (right-associative
|
|
220
|
+
| | `?:` | optional default (right-associative, see [Optionals](#optionals)) |
|
|
221
221
|
| | `if c then a else b` | inline if (the else branch extends as far right as possible) |
|
|
222
222
|
| lowest | `catch` | `x catch default` (the default extends as far right as possible) |
|
|
223
223
|
|
|
@@ -393,34 +393,9 @@ A function without a return type returns `Unit`; the last expression of a body i
|
|
|
393
393
|
|
|
394
394
|
## Types: struct, record and enum
|
|
395
395
|
|
|
396
|
-
`enum` is a union: a value that is one of several member types. `struct` and `record` are product types with named fields. A `struct` is a value type and a `record` is a reference type.
|
|
396
|
+
`enum` is a union: a value that is one of several member types. `struct` and `record` are product types with named fields. A `struct` is a value type and a `record` is a reference type (see [`struct` and `record`](#struct-and-record)).
|
|
397
397
|
|
|
398
|
-
```
|
|
399
|
-
struct Vec2(x: Float, y: Float) =
|
|
400
|
-
ZERO = Vec2(x: 0.0, y: 0.0)
|
|
401
|
-
|
|
402
|
-
fun add(self, o: Vec2) -> Vec2 =
|
|
403
|
-
Vec2(x: self.x + o.x, y: self.y + o.y)
|
|
404
|
-
|
|
405
|
-
record User(name: Str, age: Int)
|
|
406
|
-
|
|
407
|
-
record Pair[K, V](key: K, val: V)
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
The header ends with `=` only when an indented body follows: `struct Vec2(x: Float, y: Float)` with no body has no `=`.
|
|
411
|
-
|
|
412
|
-
A `struct` is a value type: copied on assignment and when passed, with fields only `Int`, `Float`, `Bool` or other structs. A `record` is a reference type: a heap object shared on assignment, with any field types, and it may be generic.
|
|
413
|
-
|
|
414
|
-
```plum
|
|
415
|
-
a := Vec2(x: 1.0, y: 2.0)
|
|
416
|
-
b := a
|
|
417
|
-
b.x = 9.0 # a.x is still 1.0: a struct is copied
|
|
418
|
-
u := User(name: "ann", age: 3)
|
|
419
|
-
u2 := u
|
|
420
|
-
u2.age = 4 # u.age is now 4: a record is shared
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
The other declaration forms are `struct` and `record` (below), `error Name = A | B` ([Error handling](#error-handling)) and `primitive Name =` ([Constants, statics and `primitive`](#constants-statics-and-primitive)).
|
|
398
|
+
The other declaration forms are `error Name = A | B` ([Error handling](#error-handling)) and `primitive Name =` ([Constants, statics and `primitive`](#constants-statics-and-primitive)).
|
|
424
399
|
|
|
425
400
|
### Enums
|
|
426
401
|
|
|
@@ -447,7 +422,7 @@ enum Json =
|
|
|
447
422
|
| Map[Str, Json]
|
|
448
423
|
```
|
|
449
424
|
|
|
450
|
-
|
|
425
|
+
Every member other than a tag is an existing type, and the enum adds no new constructor for it.
|
|
451
426
|
|
|
452
427
|
A member converts to its enum wherever the enum is expected: an argument, a `return`, a tail expression, `push`, a `T?` or a `!T` slot. Nothing is written at the call site:
|
|
453
428
|
|
|
@@ -561,7 +536,7 @@ match book
|
|
|
561
536
|
FantasyBook(title, _, _) => "Fantasy book \"{title}\" has no mythical creatures"
|
|
562
537
|
```
|
|
563
538
|
|
|
564
|
-
Patterns can be literals, a bare identifier (binds), a bare capitalized name (a member or tag of an enum, or an error name when matching an error value, also written `error.Name`; compared), a constructor pattern (`Circle(r)`, binding its fields, possibly nested), a bare member name (`Float`, which narrows the subject to that type), or `_` (wildcard). `match a, b, ...` supports multiple subjects, and a case can carry a `when <expr>` guard. A `match` over an enum must be exhaustive
|
|
539
|
+
Patterns can be literals, a bare identifier (binds), a bare capitalized name (a member or tag of an enum, or an error name when matching an error value, also written `error.Name`; compared), a constructor pattern (`Circle(r)`, binding its fields, possibly nested), a bare member name (`Float`, which narrows the subject to that type, see [Enums](#enums)), or `_` (wildcard). `match a, b, ...` supports multiple subjects, and a case can carry a `when <expr>` guard. A `match` over an enum must be exhaustive. See [`plum-examples/match.plum`](plum-examples/match.plum), [`plum-examples/dop_visitor.plum`](plum-examples/dop_visitor.plum) and [`plum-examples/oop_visitor.plum`](plum-examples/oop_visitor.plum).
|
|
565
540
|
|
|
566
541
|
Mixed kinds of value live in one enum with a `match self` method per operation:
|
|
567
542
|
|
|
@@ -596,6 +571,17 @@ struct Rect(pos: Vec2, size: Vec2) # a field may be another struct
|
|
|
596
571
|
record User(name: Str, age: Int)
|
|
597
572
|
```
|
|
598
573
|
|
|
574
|
+
The header ends with `=` only when an indented body follows: `struct Vec2(x: Float, y: Float)` with no body has no `=`.
|
|
575
|
+
|
|
576
|
+
```plum
|
|
577
|
+
a := Vec2(x: 1.0, y: 2.0)
|
|
578
|
+
b := a
|
|
579
|
+
b.x = 9.0 # a.x is still 1.0: a struct is copied
|
|
580
|
+
u := User(name: "ann", age: 3)
|
|
581
|
+
u2 := u
|
|
582
|
+
u2.age = 4 # u.age is now 4: a record is a heap object, shared on assignment
|
|
583
|
+
```
|
|
584
|
+
|
|
599
585
|
A `struct` has no generics; its fields are only `Int`, `Float`, `Bool` or other structs, and a struct can't contain itself. Values are copied on assignment and when passed, and are boxed only inside generic containers (`List[Vec2]`, `Vec2?`). A `record` may have any field types and may be generic (`record Pair[K, V](key: K, val: V)`). Methods and `match` work as for any enum. See [`plum-examples/values.plum`](plum-examples/values.plum).
|
|
600
586
|
|
|
601
587
|
## Generics
|
|
@@ -607,11 +593,11 @@ fun wrap(value: T) -> Bool =
|
|
|
607
593
|
true
|
|
608
594
|
```
|
|
609
595
|
|
|
610
|
-
A free function's generic parameters are its single-uppercase-letter type names (`T`, `U`); a type declares its own with `record Box[T]`. Generic types, methods, and free functions are duck-typed
|
|
596
|
+
A free function's generic parameters are its single-uppercase-letter type names (`T`, `U`); a type declares its own with `record Box[T]`. Generic types, methods, and free functions are duck-typed: see [How generics compile](#how-generics-compile).
|
|
611
597
|
|
|
612
598
|
### How generics compile
|
|
613
599
|
|
|
614
|
-
Generics are duck-typed and compiled per type, as in Zig. A generic body is type-checked once per concrete instantiation, and each instantiation gets its own compiled copy with unboxed values: `List[Int]` and `List[Str]` are different code, and a `List[Float]` stores raw `f64`s.
|
|
600
|
+
Generics are duck-typed and compiled per type, as in Zig. A generic body is type-checked once per concrete instantiation, and each instantiation gets its own compiled copy with unboxed values (an array of its own element type, no boxing at generic edges): `List[Int]` and `List[Str]` are different code, and a `List[Float]` stores raw `f64`s.
|
|
615
601
|
|
|
616
602
|
- **No bounds.** `[T]` carries no constraint. `fun show(x: T) -> Str = x.toStr()` works for every type that has a `toStr` method and fails at the use site otherwise.
|
|
617
603
|
- **Errors point at the use site.** A missing method is reported against the concrete type, with the chain of instantiations that led there:
|
|
@@ -623,7 +609,6 @@ in outer[Plain] (called at main.plum:9:3):
|
|
|
623
609
|
```
|
|
624
610
|
|
|
625
611
|
- **Unreachable generics are not emitted.** A generic that no root (a non-generic fn or a test) reaches is neither checked per type nor compiled.
|
|
626
|
-
- **Typed arrays are native.** Each instantiation stores its elements in an array of its own element type, with no boxing at generic edges.
|
|
627
612
|
- **Bare generic types need type arguments.** `p: Parcel` is an error when `Parcel` is generic; write `p: Parcel[Int]` (or `Parcel[T]` inside a generic).
|
|
628
613
|
- **Explicit behavior values.** When a generic needs behavior that is not a method on `T`, pass it explicitly, like a Zig comptime parameter or a Haskell dictionary:
|
|
629
614
|
|
|
@@ -733,7 +718,7 @@ for k := range scores.keys() # a fun* method of Map (also values(), entries
|
|
|
733
718
|
printLn(k)
|
|
734
719
|
```
|
|
735
720
|
|
|
736
|
-
|
|
721
|
+
Beyond `range n` (0 to n-1), the `Int` methods `a.until(b)` (a to b-1), `a.to(b)` (inclusive) and `a.downTo(b)` give other ranges (`for i := range 1.to(10)`).
|
|
737
722
|
|
|
738
723
|
### The iteration protocol
|
|
739
724
|
|
|
@@ -833,7 +818,7 @@ See [`plum-examples/seq.plum`](plum-examples/seq.plum) (one test per adapter and
|
|
|
833
818
|
|
|
834
819
|
## Optionals
|
|
835
820
|
|
|
836
|
-
An optional is `T?`: a `T`, or `null`. It is its own type, not an error: it carries no error name, and `catch` and `try` do not apply to it. A plain `T` in a `T?` slot is the present value, and `null` is the absent value wherever a `T?` is expected (a call or constructor argument, a `return`, a tail, a list element, a parameter default such as `n: Int? = null`). `null` with nothing to say what it is an optional of is an error (`x := null`: `cannot infer the type of null`). For a reference type (a class, enum, `Str`, `List`, ...) a `T?` is stored as a nullable reference, so a present value costs no allocation; for `Int`, `Float`, `Bool` and structs it is the value plus a present flag. A doubled `T??` is not allowed.
|
|
821
|
+
An optional is `T?`: a `T`, or `null`. It is its own type, not an error: it carries no error name, and `catch` and `try` do not apply to it. A plain `T` in a `T?` slot is the present value, and `null` is the absent value wherever a `T?` is expected (a call or constructor argument, a `return`, a tail, a list element, a parameter default such as `n: Int? = null`). `null` with nothing to say what it is an optional of is an error (`x := null`: `cannot infer the type of null`). For a reference type (a class, enum, `Str`, `List`, ...) a `T?` is stored as a nullable reference, so a present value costs no allocation; for `Int`, `Float`, `Bool` and structs it is the value plus a present flag, `(T..., i32)` (the `i32` is 0 when present, and the code of `null` when absent). A doubled `T??` is not allowed.
|
|
837
822
|
|
|
838
823
|
```plum
|
|
839
824
|
record Person(name: Str, age: Int)
|
|
@@ -851,7 +836,7 @@ fun find(n: Int) -> Person? =
|
|
|
851
836
|
| `while x := opt` + block | loops while the expression is non-null, re-evaluated each iteration: `while job := queue.next()` |
|
|
852
837
|
| `opt!` | the value, or crashes with `<callee> returned null at file:line:col` |
|
|
853
838
|
| `opt == null`, `opt != null` | presence test (either order) |
|
|
854
|
-
| `opt?.field`, `opt?.method()` | maps over a present value; null passes through
|
|
839
|
+
| `opt?.field`, `opt?.method()` | maps over a present value; null passes through (see [Safe navigation](#safe-navigation-)) |
|
|
855
840
|
|
|
856
841
|
`if` also takes patterns beyond a plain name (`if Circle(r) := shape`), and optional bindings work in `else if` chains: see [Pattern `if`](#pattern-if).
|
|
857
842
|
|
|
@@ -922,7 +907,7 @@ fun parse(s: Str) -> ParseError!Int =
|
|
|
922
907
|
return n
|
|
923
908
|
```
|
|
924
909
|
|
|
925
|
-
`error Name = A | B` is a top-level item; set names are unique, like type names. A set name is also a type: `e: ParseError` is a bare error value of that set, and `match e` over it is exhaustive. Pinned functions are checked against their body (`return error.Other` is an error, and so is propagating a call whose set is not a subset). A type's own generics are ordinary: `ParseError!Box[Int]` and `ParseError!Box [Int]` are the same type. Recursive functions
|
|
910
|
+
`error Name = A | B` is a top-level item; set names are unique, like type names. A set name is also a type: `e: ParseError` is a bare error value of that set, and `match e` over it is exhaustive. Pinned functions are checked against their body (`return error.Other` is an error, and so is propagating a call whose set is not a subset). A type's own generics are ordinary: `ParseError!Box[Int]` and `ParseError!Box [Int]` are the same type. Recursive functions and public std APIs pin their set (`fn 'f' is recursive: pin its error set (-> error{..}!T)`):
|
|
926
911
|
|
|
927
912
|
```plum
|
|
928
913
|
primitive Int =
|
|
@@ -997,11 +982,11 @@ An error union can have an optional as its success value: `!T?` (`error{Bad}!Int
|
|
|
997
982
|
|
|
998
983
|
### Runtime representation
|
|
999
984
|
|
|
1000
|
-
Each error name gets a global `i32` code, interned in sorted order from 1 (0 means success). An optional `T?`
|
|
985
|
+
Each error name gets a global `i32` code, interned in sorted order from 1 (0 means success). An optional `T?` uses the same layout (see [Optionals](#optionals)). A `!T` result is a multi-value return `(T..., i32)`, so neither success nor failure allocates; stored in a heap slot (a field, a `List` element, a generic container) it is boxed into a `{value, code}` struct.
|
|
1001
986
|
|
|
1002
987
|
## Testing
|
|
1003
988
|
|
|
1004
|
-
Zig-style `test` blocks are a native language feature, compiled and run only by `plum test`.
|
|
989
|
+
Zig-style `test` blocks are a native language feature, compiled and run only by `plum test`. `plum test` takes files and directories (searched recursively):
|
|
1005
990
|
|
|
1006
991
|
```plum
|
|
1007
992
|
fun add(a: Int, b: Int) -> Int =
|
|
@@ -1037,7 +1022,7 @@ hello, world!
|
|
|
1037
1022
|
## Standard library highlights
|
|
1038
1023
|
|
|
1039
1024
|
- **`Bool`** — `true`/`false`, `if`, `&&`/`||`/`!` and `==` are built in (no import); `toStr` (also used by `"{b}"` interpolation) and `Bool.parse("true")` need `import std/Bool`
|
|
1040
|
-
- **`Int`** / **`Float`** (`import std/Int`, `import std/Float`) — built-in numbers with their API in `primitive Int` / `primitive Float`: `abs`, `sign`, `min`, `max`, `sqrt`, `pow`, `floor`, `round`, `toStr`, ...
|
|
1025
|
+
- **`Int`** / **`Float`** (`import std/Int`, `import std/Float`) — built-in numbers with their API in `primitive Int` / `primitive Float`: `abs`, `sign`, `min`, `max`, `sqrt`, `pow`, `floor`, `round`, `toStr`, ... (constants and `parse`: see [Constants](#constants-statics-and-primitive))
|
|
1041
1026
|
- **`Math`** (`import std/Math`) — a `primitive` namespace of floating-point functions: `Math.sqrt`, `Math.sin`, `Math.exp`, `Math.ln`, ...
|
|
1042
1027
|
- **`Str`** (`import std/Str`) — a byte array with a codepoint-aware layer for Unicode correctness: `runeLength`, `runeAt`, `codePointAt`, `codePointToStr`
|
|
1043
1028
|
- **`Byte`** / **`ByteSlice`** (`import std/Byte`, `import std/ByteSlice`) — a scalar unsigned 8-bit value and a fixed-length mutable byte slice
|