You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After debating the whole idea with myself I have decided that standard library-level traits have too many drawbacks, and we need a proper language-level solution.
Defining a trait
A trait is defined using the trait keyword, followed by the name, followed by a list of function heads, terminated with end.
A function head is the function's name, parameters, and calling convention, without the implementation. Only the instance calling convention should be supported for now; static functions can be added in later (probably after the GC and type are implemented).
For instance, the standard library will define an Iterator trait for for loops:
trait Iterator
func has_next()
func next()
end
trait:='trait'identifierfunction_head*'end'
Implementing a trait
A trait can be implemented by any arbitrary type, by using the as keyword inside of an impl block. For instance, to implement Iterator on a user-defined Repeat struct:
struct Repeat
impl Repeat
func times(n) constructor
@n = n
end
as Iterator
func has_next()
@n >= 0
end
func next()
i = @n
@n = @n - 1
i
end
end
end
For calling methods defined on traits, we can reuse the existing method call syntax. Combined with creating a struct full of static shims that call the actual method to represent the trait, we can use the syntax Iterator.has_next(repeat) to call the method has_next on repeat's implementation of Iterator.
I considered a few other options:
repeat.Iterator.next - can be ambiguous if there are multiple traits with the same name implemented by a type
repeat.(Iterator).next, repeat.as(Iterator).next, (repeat as Iterator).next - all function the same way; they do a lookup of what next should mangle to given the trait Iterator in order to call the appropriate method on repeat, but that's unnecessary runtime work that can be done during compilation. In order to understand why the extra work is needed, we need to delve deeper into the implementation details…
The implementation
Traits should reuse the existing dtable mechanism, mainly because it's very fast (finding out a method's implementation is just an array lookup). The way this should be implemented is that function signatures get one more bit of mangling information - the ID of the trait a method is attached to (assuming that on creation a trait receives a unique ID).
The reason why the three options I presented will be hard to implement, is that we need to find out the ID of the trait at runtime. But the trait ID is known during compilation, and as such each shim method in the trait knows the ID too, so the method ID can be precomputed at compile time.
The text was updated successfully, but these errors were encountered:
After debating the whole idea with myself I have decided that standard library-level traits have too many drawbacks, and we need a proper language-level solution.
Defining a trait
A trait is defined using the
trait
keyword, followed by the name, followed by a list of function heads, terminated withend
.A function head is the function's name, parameters, and calling convention, without the implementation. Only the instance calling convention should be supported for now;
static
functions can be added in later (probably after the GC andtype
are implemented).For instance, the standard library will define an
Iterator
trait forfor
loops:Implementing a trait
A trait can be implemented by any arbitrary type, by using the
as
keyword inside of animpl
block. For instance, to implementIterator
on a user-definedRepeat
struct:Calling into trait methods
For calling methods defined on traits, we can reuse the existing method call syntax. Combined with creating a struct full of static shims that call the actual method to represent the trait, we can use the syntax
Iterator.has_next(repeat)
to call the methodhas_next
onrepeat
's implementation ofIterator
.I considered a few other options:
repeat.Iterator.next
- can be ambiguous if there are multiple traits with the same name implemented by a typerepeat.(Iterator).next
,repeat.as(Iterator).next
,(repeat as Iterator).next
- all function the same way; they do a lookup of whatnext
should mangle to given the traitIterator
in order to call the appropriate method onrepeat
, but that's unnecessary runtime work that can be done during compilation. In order to understand why the extra work is needed, we need to delve deeper into the implementation details…The implementation
Traits should reuse the existing dtable mechanism, mainly because it's very fast (finding out a method's implementation is just an array lookup). The way this should be implemented is that function signatures get one more bit of mangling information - the ID of the trait a method is attached to (assuming that on creation a trait receives a unique ID).
The reason why the three options I presented will be hard to implement, is that we need to find out the ID of the trait at runtime. But the trait ID is known during compilation, and as such each shim method in the trait knows the ID too, so the method ID can be precomputed at compile time.
The text was updated successfully, but these errors were encountered: