4 Parameterization and Parametric Polymorphism
pair_do
method:
method
pair_do[T1,T2](c1@:cons[T1], c2@:cons[T2],
closure:&(T1,T2):void):void {
eval(closure, head[T1](c1), head[T2](c2));
pair_do[T1,T2](tail[T1](c1), tail[T2](c2), closure);
}
Singly-dispatched languages do not face this verbosity, because methods are defined within a class and within the scope of the parameterized class's type parameters. Additionally, invocations of methods on a parameterized object, such as the head
message above, would not need to specify an instantiating parameter because it can be derived from the instantiating parameter of the distinguished receiver object. The following pseudo-code is representative of the kind of support found in singly-dispatched languages:
In this code, the formal type parameterclass
cons[T] {field
head:T;field
tail:list[T];method
length():int { 1 + tail.length() } ... }
T
is introduced in the cons
class declaration and is scoped over all fields and methods defined on the class. Consequently, none of these fields and methods need be explicitly parameterized, and the recursive length
call need not pass any explicit type parameters, since it is implied by the type of the receiver expression.To regain much of the conciseness of parameterization in singly-dispatched languages while still supporting multi-methods, object extensions, and other of Cecil's more flexible constructs, Cecil allows implicit type parameter bindings to be present in the type declarations of formal arguments of a method or field. These implicit type parameters are instantiated automatically with the corresponding type of the actual argument in each call site. A binding occurrence of such an implicit type variable is indicated by prefixing the type name with a back-quote character; other occurrences of the type variable simply name the bound type.
The operations on parameterized cons
objects can be rewritten with implicit type parameters as follows:
Like explicit formal type parameters, an implicit formal type parameter may be bounded from above by some type using thetemplate representation
cons[T]isa
list[T];field
head(@:cons['T]):T;field
tail(@:cons['T]):list[T] := nil[T];method
pair_do(c1@:cons['T1], c2@:cons['T2], closure:&(T1,T2):void):void { eval(closure, head(c1), head(c2)); pair_do(tail(c1), tail(c2), closure); }method
prepend(h:T, t:list['T]):list[T] {concrete object
isa
cons[T] { head := h, tail := t } }
<= type
notation, and an implicit formal parameter is quantified over all types that are subtypes of its upper bound (where any
is used as the default upper bound). Like explicit type parameters, implicit type parameters are scoped over the entire declaration. An implicit type parameter must have a name that is distinct from any other implicit or explicit type parameters. Like explicit type parameters, implicit type parameters may be used in the type declarations of earlier formal arguments, as in the prepend
method above, as long as no cyclic dependencies result. Implicit type parameters are akin to polymorphic type variables in languages like ML [Milner et al. 90]. Note that unlike 'a
type variables in ML, the back-quote in Cecil's 'T
is not part of the type name, but rather identifies that the use of the type T
is a binding occurrence as opposed to a simple use of a previously-defined type.Implicit type parameters are useful not only for parameterized types but also for performing simple calculations on argument types to compute appropriate result types. For example, the following method describes its result type in terms of its argument types:[16]
method
min(x1:'T1 <= comparable, x2:'T2 <= comparable):T1|T2 {
if(x1 < x2, { x1 }, { x2 }) }
User-defined control structures often compute the types of their results from the types of their arguments:
As illustrated by the above examples, least-upper-bound types over implicit type parameters are relatively common when expressing the type of a method's result in terms of its arguments' types.signature
if(condition:bool, true_case:&():'T1, false_case:&():'T2):T1|T2;method
if(condition@:true, true_case:&():'T1, false_case:&():void):T1 { eval(true_case) }method
if(condition@:false, true_case:&():void, false_case:&():'T2):T2 { eval(false_case) }
Implicit type parameter bindings can only appear in the declared type of a formal parameter or variable, as the upper bound type of another type parameter, or as the instantiating parameter of a parameterized object or type being augmented in an extension declaration. A type that can contain an implicit type parameter binding is called a type pattern. The syntax of several constructs is updated to reflect where implicit type parameter bindings are legal:
type_pattern ::= binding_type | named_type_p | closure_type_p | lub_type | glb_type | paren_type binding_type ::= "'" name ["<=" type_pattern] named_type_p ::= name [param_patterns] closure_type_p ::= "&" "(" [arg_type_ps] ")" [type_decl_p] signature_decl ::= "signature" method_name "(" [arg_type_ps
] ")" [type_decl] ";" field_sig_decl ::= ["var"] "field" "signature" msg_name [formal_params] "("arg_type_p
")" [type_decl] ";" arg_type_ps ::= arg_type_p { "," arg_type_p } arg_type_p ::= [name ":"] type_pattern specializer ::= location [type_decl_p
] specialized formal | [type_decl_p
] unspecialized formal | "@" ":" named_object_p sugar for@named_obj_p :named_obj_p
closure_formal ::= [name] [type_decl_p
] formal names are optional, if never referenced type_ext_decl ::= "extend" "type"named_type_p
{type_relation} ";" obj_ext_decl ::= "extend" extend_kindnamed_object_p
{relation} [field_inits] ";" type_relation ::= "subtypes"type_patterns
parents ::=named_object_p
{ ","named_object_p
} named_object_p ::= name [param_patterns] type_decl_p ::= ":" type_pattern formal_param ::= ["'"] name [ "<="type_pattern
] param_patterns ::= "[" type_patterns "]" type_patterns ::= type_pattern { "," type_pattern }
min
to use a more sophisticated comparable
type.
Generated with Harlequin WebMaker