Paste: clay type parameterization and requirements
Author: | kssreeram |
Mode: | factor |
Date: | Mon, 17 May 2010 23:32:12 |
Plain Text |
Any Clay procedure can be prefixed with one or more 'pattern variables' which can then be used for specifying argument types as patterns. Pattern variables are similar to template parameters in C++ except that these pattern variables are bound by pattern matching against the types of actual arguments at the call site.
Arbitrary predicates can be written to specify requirements on pattern variables. Any user defined procedure returning a boolean can be used as a predicate.
Here is a generic version of factorial:
[T | Integer?(T)]
factorial(n:T) {
if (n <= T(1))
return T(1);
return n * factorial(n - T(1));
}
The first line is read as "T, such that Integer?(T)".
To call factorial simply write "factorial(7)". The pattern variable 'T' will be bound to 'Int' because the literal '7' is of type 'Int'. If instead 'factorial' is called with a value of arbitrary-precision integer type then T will be bound to the arbitrary-precision integer type.
Clay supports overloading of procedures based on arbitrary predicates. (This is more powerful than the constrained form of overloading enabled by haskell type-classes.)
The compiler generates efficient code because all procedures are specialized to the types of the arguments. I have conducted a few performance tests which indicate that generic programs written in Clay perform the same as equivalent non-generic versions written in C.
Author: | kssreeram |
Mode: | factor |
Date: | Mon, 17 May 2010 23:34:33 |
Plain Text |
Any Clay procedure can be prefixed with one or more 'pattern
variables' which can then be used for specifying argument types as
patterns. Pattern variables are similar to template parameters in C++
except that these pattern variables are bound by pattern matching
against the types of actual arguments at the call site.
Arbitrary predicates can be written to specify requirements on pattern
variables. Any user defined procedure returning a boolean can be used
as a predicate.
Here is a generic version of factorial:
[T | Integer?(T)]
factorial(n:T) {
if (n <= T(1))
return T(1);
return n * factorial(n - T(1));
}
The first line is read as "T, such that Integer?(T)".
To call factorial simply write "factorial(7)". The pattern variable
'T' will be bound to 'Int' because the literal '7' is of type
'Int'. If instead 'factorial' is called with a value of
arbitrary-precision integer type then T will be bound to the
arbitrary-precision integer type.
Clay supports overloading of procedures based on arbitrary
predicates. (This is more powerful than the constrained form of
overloading enabled by haskell type-classes.)
The compiler generates efficient code because all procedures are
specialized to the types of the arguments. I have conducted a few
performance tests which indicate that generic programs written in Clay
perform the same as equivalent non-generic versions written in C.
Author: | kssreeram |
Mode: | factor |
Date: | Mon, 17 May 2010 23:37:07 |
Plain Text |
The Clay approach to type parametrization and requirements sounds
interesting. I found that for my Haskell experiments I had to use many
of the GHC pragmas related to type classes. For example, for Chapter
5, I needed TypeFamilies, FlexibleInstances, UndecidableInstances,
MultiParamTypeClasses, and FlexibleContexts. But it was possible.
Do you have something like type functions, so that requirements can be
expressed in terms of a parameter type and some affiliated type? A
good example is the distance type for an iterator type.
Author: | kssreeram |
Mode: | factor |
Date: | Tue, 18 May 2010 01:47:42 |
Plain Text |
Yes, there is very good support for type functions in Clay. In
addition to regular arguments, any Clay procedure can have 'static'
arguments. Static arguments are directly pattern matched with the
value specified at the call site and can result in one or more pattern
variables being bound. Static arguments are useful for writing type
functions.
Here is an example. Note: overloadable procedures are declared using
the 'overloadable' keyword. Each overload case is separately specified
using the 'overload' keyword.
overloadable SequenceElementType;
overload SequenceElementType(static String) {
return Char;
}
[T,n]
overload SequenceElementType(static Array[T,n]) {
return T;
}
[T]
overload SequenceElementType(static Vector[T]) {
return T;
}
To call this procedure, simply pass a type as the first argument
"SequenceElementType(Vector[Int])". 'SequenceElementType' can now be
used as part of predicate expressions.
As can be seen in this example, types can be returned from a
procedure. This is possible because every type, T, is a first class
value of a unique singleton type. Because the type of a type is a
unique singleton type no runtime representation is required.
Being able to use arbitrary user defined code as part of type
requirement specifications is a big advantage in Clay. This gives a
lot of power and simplicity not available with Haskell type classes or
C++ concepts. This is made possible because an interpreter for the
full Clay language is available as part of the compiler.
New Annotation