plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
be00b92
— Peter John
2026-11-01T17:31:34+05:30
T4: README
README.md
CHANGED
|
@@ -29,9 +29,7 @@ enum Shape = # a sum type; payload fields are name
|
|
|
29
29
|
Circle(r) => r * r * 3.14
|
|
30
30
|
Rect(w, h) => w * h
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
struct Vec2(x: Float, y: Float) = # small copy-semantics value type
|
|
33
|
-
| Vec2(x: Float, y: Float)
|
|
34
|
-
|
|
35
33
|
ZERO = Vec2(x: 0.0, y: 0.0) # Vec2.ZERO
|
|
36
34
|
|
|
37
35
|
fun add(self, o: Vec2) -> Vec2 = # `a + b` calls a.add(b)
|
|
@@ -77,7 +75,8 @@ test "glance"
|
|
|
77
75
|
| a conditional expression | `if c then a else b` (there is no `?:` ternary) |
|
|
78
76
|
| loop | `while c`, `for x := range src`, `for i := range n` |
|
|
79
77
|
| a sum type / record | `enum Shape = \| Circle(r: Float) \| Rect(w: Float, h: Float)` |
|
|
80
|
-
| a small value type | `
|
|
78
|
+
| a small value type | `struct Vec2(x: Float, y: Float)` |
|
|
79
|
+
| a record (reference type) | `record User(name: Str, age: Int)` |
|
|
81
80
|
| a method / static / constant | `fun area(self)` / `fun origin()` / `NAME = expr` inside the type body |
|
|
82
81
|
| generics | `fun id(x: T) -> T`, `enum Box[T]`, duck-typed, one copy per type |
|
|
83
82
|
| operators on your types | define `add`, `sub`, `mul`, `div`, `neg`, `eq`, `lt`, `le`, `gt`, `ge` |
|
|
@@ -244,9 +243,7 @@ On `Int`, `Float`, `Byte` and `Str` operators are built in. On any other (declar
|
|
|
244
243
|
Each operator needs its own method (no derivation from `lt`/`eq`). The right operand may have a different type: `v * 2.0` calls `mul(self, s: Float)`. Using an operator without the method is an error such as `Vec2 has no operator '+' (define add)`, with the instantiation chain when it happens inside a generic. `==`/`!=` on a type with no `eq` keep their built-in meaning (variant identity).
|
|
245
244
|
|
|
246
245
|
```plum
|
|
247
|
-
enum Vec2 =
|
|
248
|
-
|
|
246
|
+
struct Vec2(x: Float, y: Float) =
|
|
249
|
-
|
|
250
247
|
fun add(self, o: Vec2) -> Vec2 =
|
|
251
248
|
Vec2(x: self.x + o.x, y: self.y + o.y)
|
|
252
249
|
|
|
@@ -340,7 +337,7 @@ match job
|
|
|
340
337
|
Details:
|
|
341
338
|
|
|
342
339
|
- The bindings exist only in that branch's block (and in the `, cond`s after the pattern); using them after the `if`, or in a later `else` branch, is an error.
|
|
343
|
-
- A pattern that cannot fail on its type (`Vec2(x, y) := v` on a
|
|
340
|
+
- A pattern that cannot fail on its type (`Vec2(x, y) := v` on a struct) is allowed and always takes the branch.
|
|
344
341
|
- `else if` chains can mix plain conditions, optional bindings (`if x := opt`, see [Optionals](#optionals)) and patterns. A plain lowercase name over a `T?` is the optional binding; any other pattern is a pattern test.
|
|
345
342
|
- It works on error values too: inside a `catch e` block, `if TooBig := e` tests the error name.
|
|
346
343
|
|
|
@@ -367,16 +364,20 @@ fun sumAll(nums: ...Int) -> Int = # variadic param
|
|
|
367
364
|
|
|
368
365
|
A function without a return type returns `Unit`; the last expression of a body is its value. A call can name its arguments only for constructors (`Cat(name: "Whiskers", age: 3)`); functions take positional arguments.
|
|
369
366
|
|
|
370
|
-
## Types:
|
|
367
|
+
## Types: struct, record and enum
|
|
371
368
|
|
|
372
|
-
`enum` is
|
|
369
|
+
`enum` is a sum type: a value that is one of several variants. `struct` and `record` are product types: one variant named like the type, with named fields. A `struct` is a value type and a `record` is a reference type.
|
|
373
370
|
|
|
374
371
|
```plum
|
|
375
|
-
|
|
372
|
+
struct Vec2(x: Float, y: Float) =
|
|
376
|
-
|
|
373
|
+
ZERO = Vec2(x: 0.0, y: 0.0)
|
|
377
374
|
|
|
378
|
-
|
|
375
|
+
fun add(self, o: Vec2) -> Vec2 =
|
|
376
|
+
Vec2(x: self.x + o.x, y: self.y + o.y)
|
|
377
|
+
|
|
379
|
-
|
|
378
|
+
record User(name: Str, age: Int)
|
|
379
|
+
|
|
380
|
+
record Pair[K, V](key: K, val: V)
|
|
380
381
|
|
|
381
382
|
enum Color =
|
|
382
383
|
| Red
|
|
@@ -396,7 +397,20 @@ enum Step(n: Int) = # a shared field on every variant ...
|
|
|
396
397
|
| ReadMax(20)
|
|
397
398
|
```
|
|
398
399
|
|
|
399
|
-
The
|
|
400
|
+
The header ends with `=` only when an indented body follows: `struct Vec2(x: Float, y: Float)` with no body has no `=`.
|
|
401
|
+
|
|
402
|
+
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.
|
|
403
|
+
|
|
404
|
+
```plum
|
|
405
|
+
a := Vec2(x: 1.0, y: 2.0)
|
|
406
|
+
b := a
|
|
407
|
+
b.x = 9.0 # a.x is still 1.0: a struct is copied
|
|
408
|
+
u := User(name: "ann", age: 3)
|
|
409
|
+
u2 := u
|
|
410
|
+
u2.age = 4 # u.age is now 4: a record is shared
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
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)).
|
|
400
414
|
|
|
401
415
|
### Methods, `self` and field access
|
|
402
416
|
|
|
@@ -476,22 +490,21 @@ enum Shape =
|
|
|
476
490
|
|
|
477
491
|
See [`plum-examples/shapes.plum`](plum-examples/shapes.plum) and [`plum-examples/types.plum`](plum-examples/types.plum).
|
|
478
492
|
|
|
479
|
-
### `
|
|
493
|
+
### `struct` and `record`
|
|
480
494
|
|
|
481
|
-
`
|
|
495
|
+
`struct` declares a small, copy-semantics value type (a vector, a color, a rectangle) that is flattened into wasm locals instead of being heap-allocated. `record` declares a reference type. Both have one variant named like the type:
|
|
482
496
|
|
|
483
497
|
```plum
|
|
484
|
-
value enum Vec2 =
|
|
485
|
-
|
|
498
|
+
struct Vec2(x: Float, y: Float) =
|
|
486
|
-
|
|
487
499
|
fun add(self, o: Vec2) -> Vec2 =
|
|
488
500
|
Vec2(x: self.x + o.x, y: self.y + o.y)
|
|
489
501
|
|
|
490
|
-
value enum Rect =
|
|
491
|
-
|
|
502
|
+
struct Rect(pos: Vec2, size: Vec2) # a field may be another struct
|
|
503
|
+
|
|
504
|
+
record User(name: Str, age: Int)
|
|
492
505
|
```
|
|
493
506
|
|
|
494
|
-
|
|
507
|
+
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).
|
|
495
508
|
|
|
496
509
|
## Generics
|
|
497
510
|
|
|
@@ -545,7 +558,7 @@ sumWith(List(1.5, 2.5), floatNum) # 4.0
|
|
|
545
558
|
|
|
546
559
|
## Constants, statics and `primitive`
|
|
547
560
|
|
|
548
|
-
There are no top-level constants and no free-standing statics. A constant lives inside a type's body (`enum`, `
|
|
561
|
+
There are no top-level constants and no free-standing statics. A constant lives inside a type's body (`enum`, `struct`, `record`) or a `primitive`; `NAME = expr` declares an associated constant, and a `fun` without `self` is a static function. Both are reached through the type, `Type.NAME` and `Type.f(args)`, from anywhere, and by their bare name inside the same body. A constant is evaluated lazily, once (a heap value such as a `List` is cached and shared), and names are unique per type, so `Alpha.X` and `Beta.X` coexist.
|
|
549
562
|
|
|
550
563
|
```plum
|
|
551
564
|
enum Level =
|
|
@@ -557,9 +570,7 @@ enum Level =
|
|
|
557
570
|
fun cap(self, n: Int) -> Int =
|
|
558
571
|
if n > LIMIT then LIMIT else n
|
|
559
572
|
|
|
560
|
-
value enum Vec2 =
|
|
561
|
-
|
|
573
|
+
struct Vec2(x: Float, y: Float) =
|
|
562
|
-
|
|
563
574
|
ZERO = Vec2(x: 0.0, y: 0.0) # Vec2.ZERO
|
|
564
575
|
|
|
565
576
|
fun unit() -> Vec2 = # a static: Vec2.unit()
|
|
@@ -732,7 +743,7 @@ See [`plum-examples/seq.plum`](plum-examples/seq.plum) (one test per adapter and
|
|
|
732
743
|
|
|
733
744
|
## Optionals
|
|
734
745
|
|
|
735
|
-
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
|
|
746
|
+
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.
|
|
736
747
|
|
|
737
748
|
```plum
|
|
738
749
|
enum Person =
|