kitten
kitten copied to clipboard
Field names in types
Large structures are unwieldy to use without named fields. I propose we reuse the as
notation for specifying field names.
type Point3D:
case point3D:
x as Float64
y as Float64
z as Float64
I think it’s good to impose the restriction that fields are either all named, or all anonymous. Next, we need a syntax and semantics for field access, and the conventional .name
notation is as good as anything.
1.0 2.0 3.0 point3D .x // 1.0
1.0 2.0 3.0 point3D -> p;
p.x // 1.0
Initially, field accesses will not be polymorphic—x.y
means “look up the index of the field y
in the inferred type of x
and generate the appropriate match
expression”. If the field isn’t present, or the inferred type is polymorphic, the compiler will produce an error.
1.0 2.0 3.0 point3D .x
// ==
1.0 2.0 3.0 point3D match case point3D -> x, _, _ { x }
This restriction can be relaxed later, for example if we add extensible records.
If the same field name is present in multiple constructors of a type, then the field must have the same type in each.
type Animal:
case dog:
age as Float64
case cat:
age as Float64
7.5 dog .age // Float64
type Animal:
case dog:
age as Float64
case cat:
age as Int32 // Error: field 'age' has different types in different cases
If the field is present in some cases but not others, a field access produces an Optional
.
type Job:
case butcher
case baker
case candlestick_maker
case programmer
case beggar
type Animal:
case human:
name as Name
job as Job
case cat:
name as Name
define get_a_job (Animal -> Job):
.job // match case human -> _, job { job some } else { none }
beggar from_optional
We may also need syntax for initialising and replacing fields by name, like Haskell’s record notation.
data Animal = Human { name :: Name, job :: Job } | Cat { name :: Name }
sam, sammer :: Animal
sam = Human { name = "Sam", job = Programmer }
sammer = sam { name = "Sammer" }
But so far I haven’t been able to come up with anything satisfactory for Kitten.
It looks like we can represent an expression .label
with a Get
constructor for Term
, then infer the type as α → α::label, that is, whatever the type of label
is in α. Zonking does the actual lookup and error reporting, and then the Get
term can be desugared to a match
after inference. α can be any “concrete type”, which I guess is just a type constructor or constructor application.
For updating fields, I’m considering a notation like .(value -> name)
, or .(-> name)
to take the value from the stack, but this doesn’t quite work for initialisation.