feat(lang): L5 generic records and functions
property Pool<T> { ... }, function first<T>(xs: []T) -> T, map<T, U> over fn
types; a type writes an instance as Pool<Thing>, nested as deep as needed. The
parser names an instance Pool$Thing and remembers its generic and arguments; the
checker takes the generic declarations out, infers a call's type arguments from
its arguments or its result's declared slot, and makes each instance once as an
ordinary record or function, checked like any other. Errors print Pool<Thing>.
An instance keeps its generic's module and export (L3). ludic-fmt keeps type
arguments together while spacing comparisons and shifts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
57b66bdf47
commit
6a24b14f0e
26 changed files with 61796 additions and 51949 deletions
66
examples/lang/generics.ludic
Normal file
66
examples/lang/generics.ludic
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# generics.ludic — L5: generic records and functions. A record takes type parameters
|
||||
# (`property Pool<T>`), a function takes them after its name (`function first<T>`), and a call's
|
||||
# are worked out from its arguments or, where there are none to say, from the slot its result is
|
||||
# written into. Each instance is an ordinary record or function the compiler makes once.
|
||||
#
|
||||
# Running it prints: 7 Crater Lake a 2 west 7 0.75 2.5 1
|
||||
program Generics {
|
||||
numbers float
|
||||
property Pool<T> {
|
||||
items: []T = null
|
||||
n: int = 0
|
||||
}
|
||||
property Pair<A, B> {
|
||||
a: A
|
||||
b: B
|
||||
}
|
||||
property Maker<T> { make: fn(int) -> T }
|
||||
property Trail { name: string = "", weight: float = 0.0 }
|
||||
function pool_new<T>() -> Pool<T> {
|
||||
let p = new Pool<T>
|
||||
p.items = new []T
|
||||
return p
|
||||
}
|
||||
function pool_add<T>(p: Pool<T>, x: T) -> void {
|
||||
push(p.items, x)
|
||||
p.n += 1
|
||||
}
|
||||
function pool_at<T>(p: Pool<T>, i: int) -> T { return p.items[i] }
|
||||
function last_of<T>(p: Pool<T>) -> T { return pool_at(p, p.n - 1) }
|
||||
function first<T>(xs: []T) -> T { return xs[0] }
|
||||
function map<T, U>(xs: []T, f: fn(T) -> U) -> []U {
|
||||
let out = new []U
|
||||
var i = 0
|
||||
while i < len(xs) {
|
||||
push(out, f(xs[i]))
|
||||
i += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
function half(n: int) -> float { return float(n) / 2.0 }
|
||||
function size(s: string) -> int { return len(s) }
|
||||
function rope(n: int) -> Trail {
|
||||
let t = new Trail
|
||||
t.name = "rope"
|
||||
t.weight = float(n) * 0.25
|
||||
return t
|
||||
}
|
||||
entry {
|
||||
let ints: Pool<int> = pool_new()
|
||||
pool_add(ints, 3)
|
||||
pool_add(ints, 4)
|
||||
let trails: Pool<Trail> = pool_new()
|
||||
let t = new Trail
|
||||
t.name = "Crater Lake"
|
||||
pool_add(trails, t)
|
||||
let nested: Pool<Pool<int>> = pool_new()
|
||||
pool_add(nested, ints)
|
||||
let pr = new Pair<string, int>
|
||||
pr.a = "west"
|
||||
pr.b = 7
|
||||
let m = new Maker<Trail>
|
||||
m.make = fn rope
|
||||
let hs = map([2, 5], fn half)
|
||||
print(`{pool_at(ints, 0) + last_of(ints)} {pool_at(trails, 0).name} {first(["a", "b"])} {last_of(nested).n} {pr.a} {pr.b} {m.make(3).weight} {hs[1]} {len(map(["a"], fn size))}`)
|
||||
}
|
||||
}
|
||||
8
examples/rejected/generic_arity.ludic
Normal file
8
examples/rejected/generic_arity.ludic
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# L5: a generic takes as many type arguments as it has parameters
|
||||
program GenericArity {
|
||||
property Pool<T> { n: int = 0 }
|
||||
entry {
|
||||
let p = new Pool<int, string>
|
||||
print(p.n)
|
||||
}
|
||||
}
|
||||
9
examples/rejected/generic_mismatch.ludic
Normal file
9
examples/rejected/generic_mismatch.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# L5: two instances of one generic are two types
|
||||
program GenericMismatch {
|
||||
property Pool<T> { n: int = 0 }
|
||||
entry {
|
||||
let ints = new Pool<int>
|
||||
let words: Pool<string> = ints
|
||||
print(words.n)
|
||||
}
|
||||
}
|
||||
9
examples/rejected/generic_unbound.ludic
Normal file
9
examples/rejected/generic_unbound.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# L5: a call whose type arguments nothing decides is refused - give the result a declared type
|
||||
program GenericUnbound {
|
||||
property Pool<T> { n: int = 0 }
|
||||
function pool_new<T>() -> Pool<T> { return new Pool<T> }
|
||||
entry {
|
||||
let p = pool_new()
|
||||
print(p.n)
|
||||
}
|
||||
}
|
||||
9
examples/rejected/private_generic.ludic
Normal file
9
examples/rejected/private_generic.ludic
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# L5 + L3: an instance keeps its generic's module and export - a private generic stays private
|
||||
import "private_kit"
|
||||
program PrivateGeneric {
|
||||
entry {
|
||||
let p = new Pool<int>
|
||||
grow(p)
|
||||
print(pool_len(p))
|
||||
}
|
||||
}
|
||||
2
examples/rejected/private_kit/index.ludic
Normal file
2
examples/rejected/private_kit/index.ludic
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
module kit
|
||||
import "pool.ludic"
|
||||
11
examples/rejected/private_kit/pool.ludic
Normal file
11
examples/rejected/private_kit/pool.ludic
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export property Pool<T> {
|
||||
items: []T = null
|
||||
}
|
||||
function grow<T>(p: Pool<T>) -> void {
|
||||
if p.items == null { p.items = new []T }
|
||||
}
|
||||
export function pool_add<T>(p: Pool<T>, x: T) -> void {
|
||||
grow(p)
|
||||
push(p.items, x)
|
||||
}
|
||||
export function pool_len<T>(p: Pool<T>) -> int { return len(p.items) }
|
||||
Loading…
Add table
Add a link
Reference in a new issue