stz requires
Currently traits have a property called requires: which is an array of method signatures. We need a way to specify more than just implementations that must exist for a class to be part of a trait group.
Let's segway a moment and figure out what a class even is.
person = ( name: string, age: (quantity of: seconds) )
We should be able to define a structure to describe structures or else we cannot have proper reflection at compile time.
class = (
name: string,
alignment: uint,
members: (fixed-map of: string to: class-member),
traits: (array of: method-signature,) )
class-member = ( offset: uint, type: class, )
From this we can see we can query type information from a class at compile time (and even runtime) easily:
person-name-type = person members['name'] type
I like the idea of 'write it only once' – but it's not always practical. Let's say we wanted to make a getter for the name. We can define it ultra specifically:
[ person name | &person → person members['name'] type | person.name ]
Now if we changed the type in the class definition the method would be automatically correct too. Yay? There is such a thing as too much meta perfection.
This is useful though if we wanted to get information about, say, a variants element-class. We're going to need a shorter hand to get at it though. I don't know about you but writing out that big expression every time I'm doing meta-programming would drive me batty.
Let's start by adding some extra information to classes:
class = (
name: string,
alignment: uint,
members: (fixed-map of: string to: class-member),
constants: (fixed-map of: string to: any),
traits: (array of: method-signature,) )
We've had to introduce a new very powerful type called 'any' which can be any object. The requirement here is it stores its type information alongside its data. That might be implemented something like this:
any = class (type: class, memory-address of: u8,)
Here be dragons. We'd have to convert the object in to straight bytes in memory to store it like this. That's how these things are done but if we can avoid that kind of programming the more power to us.
Now we have constants on every class. We still need to decide what it looks like to call methods about classes instead of methods about objects. Should it be a class call? should it be a specific kind of syntax? should it be a property on the class itself?
What about being able to define methods that take a specific object rather than a class of objects? What if you could write code like this:
[ true not | true → false | false ]
One reason I've been avoiding that so far is how rare it is. In functional programming you do it in a few specific places - usually when you want to deal with the null-version of a thing: 'If you have an empty list do this, otherwise do that'.
In stz it'd only work with things known at compiler time. That makes it a little less useful. Also the idea of [ signature | types | code ] is to define something to be used at runtime. If we define things to be used at compile time are we leaving around stuff that needs to be trimmed out of the final executable? Is that okay? to have list of: sitting about in the same space as the program that will run? technically we can call it - we likely can't do anything with it though.
If we look at the method definition a different way: [ signature | types as they will be at runtime | code ] then we can get a better picture of what things don't quite make sense at compile time. We want to be able to name types at compile time. We want 'compile time person' not 'runtime person'.
For now we shall stick with sending class to it. It's the same facility we'd use at runtime so let's stick with it:
list-t = { trait | constants: ('element-class': class,) }
[ list-t-class of: element-class | list-t class, object-t -> class |
{ class |
traits: (
list-t of: element-class,
ordered-t of: element-class,
iterable-t of: element-class,)
constants: ('element-class': element-class,)
variables: (
elements: &array of: element-class,
length: uint,
allocator: &memory-allocator,)
}
]
iterable-t = { trait |
constants: ('element-class': class,)
methods: (
[ iterable length | iterable-t -> uint ],
[ iterable[index] | iterable-t, uint -> iterable element-class ] }
This almost works except we can't yet call element-class on the list-t variant. We can fix that though. Just like the of: method above we can implement element-class.
[ list-t-class element-class | list-t class -> class |
list-t-class constants['element-class'] ]
Now it'll work. The only magic here is type conversion from any back to class in the constants map.
All this to write out the long form. Is there a short form? There has to be! we started this journey to easily define getters and setters and we're still no where near doing that easily. But we can do it all by hand now if we wanted to.
Next step will be writing the convenience magic.