Closed ghost closed 12 years ago
Very nice. Does the shunting yard algorithm with post-variadic matching improve compile performance much?
As far as requiring parens around variadic operator arguments goes, I think that's OK.
I haven't noticed a change in performance. I suppose the potential exists, although it's unlikely to ever be an issue.
What would be nice though is to improve overload resolution and pattern matching performance. Not sure how to achieve that at the moment though.
One way to improve overload resolution performance would be to add some indexing so that it doesn't need to do a linear search through the entire overload set on every call. You could index the root symbol of each argument position and then use those indices to filter the overload set before doing the linear search. If you have overloads like these:
overload foo(x:Int, y:Float) {} //A
[T when Integer?(T)] overload foo(x:Vector[T], y:Float) {} //B
[T] overload foo(x:Vector[T], y:Int) {} //C
and you're looking for foo(Vector[Int], Float)
, then the arg-0 index would find B
and C
, and the arg-1 index would find A
and B
, so you'd only then have to match B
.
I just did a before-and-after comparison of the test suite and I see 327s before, 279s after, which is a pretty good speedup.
Excellent. Btw, do you know what happened to dembits?
I think claywheel has some bugs that are causing it to fall over and stop updating. I'll ping @erg to try to start his claywheels again.
A bit of profiling shows the bottleneck in overload resolution to be predicate evaluation, not pattern matching or size of the overload set.
Interesting find. Just goes to show you should always measure first. I think the evaluator could benefit from a better allocation policy. Right now I think static values are always allocated as ValueHolders, which are refcounted and allocated on the heap, when in most cases they could probably be alloca
ed or allocated on a special stack during the normal course of evaluation (since compile-time local values have stack extent just like run-time local values). ValueHolders should only be necessary when a static value becomes part of an InvokeEntry, symbol instance, or other global state.
If you want to get more ambitious in language design, concepts/type classes/interfaces/traits/whatever you want to call them could be a useful feature that, in addition to be prettier and more theoretically sound than predicates for many generic programming patterns, could also greatly improve compile performance. Matching types to traits could potentially be a lot faster than testing predicates.
Maybe something like this:
define Integer {
integerAdd(a, b);
integerSubtract(a, b);
etc.
}
[T:Integer]
define Number[T] { // defines interfaces for type-class Number
(+)(a:T, b:T) : T;
}
[T:Number[T]]
define Foo[T] { // defines interfaces for type-class Foo which is dependent on Number
bar(a:T, b:T) : T;
}
registertypes Integer (Int32);
[T:Integer, U:Integer]
overload (+)(a:T, b:U) : T = integerAdd(a, b);
[T]
overload bar(a:T, b:T) : T = a+b;
[T:Foo[T]] // Check for implementation of bar for type T
someFunc(a:T, b:T) = bar(a, b);
main () {
var a:Int, b:Int = 2, 4;
var c = someFunc(a, b); // Works
var d:Int64, e:Int64 = 2l, 4l;
var f = someFunc(d, e); // Doesn't work as Int64 not registered as Integer
}
Here are some notes from an earlier attempt to design a type class feature: https://github.com/jckarter/clay/wiki/Protocols I like the idea of reusing define
—independent generic functions can behave as single-function type classes (an overload defines an instance), and you could just extend it to let you define a group of related generics as a group. You shouldn't need anything like registertypes
—a type becomes an instance of a class by providing implementations of its member functions.
A good test case is to try defining the sequence and iterator protocols, since they require parametric classes and benefit from support for functional dependencies.
The previously proposed 'of' syntax isn't great. Maybe re-use of define
and as
could work (although of
could also be used in this context without the definition/constraint mash-up) e.g. define T as TypeClass {}
and all pattern constraints could be specified with the pattern definitions. Using this the protocols would maybe look like this:
// Protocol definition using typeclasses
[I, E]
define I as IteratorType[E] {
// required operations
next(i:I) : E;
hasNext?(i:I) : Bool;
// derived definitions
IteratorTargetType(I) = E;
}
[S, E, I:IteratorType[E]]
define S as SequenceType[I] {
// required operations
iterator(x:S) : I;
// derived definitions
SequenceIteratorType(S) = I;
SequenceElementType(S) = IteratorTargetType(I);
}
[C, E]
define C as CoordinateType[E] {
dereference(c:C) : E;
inc(ref c:C) : ;
dec(ref c:C) : ;
CoordinateTargetType(C) = E;
}
[E, C:CoordinateType[E]]
define C as RandomAccessCoordinateType[E] {
[I:Integer] add(c:C, i:I) : C;
[I:Integer] subtract(c:C, i:I) : C;
subtract(c:C, c:C) : Int;
}
[S, E, C:CoordinateType[E]]
define S as CoordinateSequenceType[C] {
begin(s:S) : C;
end(s:S) : C;
SequenceCoordinateType(S) = C;
}
This seems pretty consistent with the current Clay syntax and would still allow predicate definitions like [T:Integer when T==Int64]
. One possible issue is specification of multiple type-class constraints on a single pattern. I do like the idea of type-classes being pervasive with no explicit requirement for formal instance definitions.
So . . .
[T]
define T as FooType {
foo?(x:T, y:Int) : Bool;
}
overload foo?(x:Foo, y:Int) = x[y] == 0; // Foo is now implicitly an instance of FooType
I haven't really given much thought to type-class extension. I suppose you could overload
the type-class definition. Any thoughts on this?
This looks pretty decent. You could support multiple constraints on a single variable by perhaps allowing a variable to be declared multiple times with different constraints. Supporting multi-in-parameter classes could also be useful (though it brings up annoying semantic and implementation challenges). For example:
[A:Foo, A:Bar, (A, B):Bas]
could constrain A
as an instance of Foo
and Bar
, and A
and B
together as an instance of Bas
. For type class extension, would it be sufficient to declare a constraint on the extending class? For example:
[T:Base]
define T as Derived {
...
}
would declare Derived
as a subclass of Base
.
I created issue #382 for further discussion/implementation talk.
I'm not shure if I like type classes. but that's mainly because i can't imagine how well things play together. specifically type classes, predicates, records, tuples and other statics in combination. possibly you can imagine full examples for me?
say, I want to write a labyrinth game with walls, keys and stuff. say, I want the development most intuitive so that I could possibly write things like:
[O where isWall?(O)] overload isMovable?(O) = false;
(...)
if ( isMovable?(obj) ) { moveOn(obj, directionOf(p1)); moveOn(p1, directionOf(p1)); } else beep();
silly example, I know! the point is that I'd like to have an interface very native to the toy I am programming. Below that interface, the situation is very different. Because the labyrint is a grid and all graphics share the same size and basic logics, there could be only one record named gameObject (or so) that can represent all the different objects. basically, it holds a pointer to a set of different pixmap objects for different animations, if any, an ID field to identify the type of that object (wall, key...) and some state. besides there are some generic interfaces like above, which are all the same for all objects i.e. isDeadly?, isFetchable?, destroy, moveOn, triggerAnim, whatever...
currently I'd have a problem to distinct the objects if the record is the same. records are also not clone'able or subtype'able. there are no predicates for checking aliases and so forth. type classes now seem to solve the issue. but how do they if the only suitable difference for a check is the ID in the record? how would I subtype the objects to make them distinct though their memory layout is the same? got the point?
can someone please show me how type classes play in here and how i should lay out the code?
sorry, got the page wrong, have posted twice in #382
Use a modified shunting yard algorithm to parse/eval infixOperator expressions. Handles multivalue arguments. However, due to precedence of the unpack operator an unpack expression must be used in parens e.g.
var ..a = integers(#4); var b = 3 + (..a);
Operator test updated.