Better Abstraction Mechanisms
Ordinary (first-order, monomorphic) functions allow us to abstract operations away from specific values.
These two functions are fundamentally similar. Can we take advantage of this by making them instances of a single, more abstract, function?
Functional languages permit us to build more powerful abstractions in several ways.
Polymorphism allows us to write functions that work uniformly
on container data structures without regard for the contained type.
Higher-order functions allow us to define functions that share a uniform
control structure as instances of a single function.
Polymorphism
Higher-order abstractions must be written explicitly, but polymorphism comes ``for free.''
Consider these functions:
The first two make perfectly good sense on lists containing any type. The last makes sense on lists containing pairs of any type. Moreover, the function ``does the same thing'' to its argument list regardless of of the underlying types.
Note that SML automatically gives them types in which the ``don't care''
components are denoted by type variables 'a,'b, ,
pronounced ``alpha, beta,
''.
Unless we explicitly add type constraints, SML will always give every function the most general possible type, allowing the broadest possible re-use.
Type Inference
ML automatically infers the most general (a.k.a. principal type) of every variable and expression in the program. Of course, a given expression might have lots of legal types, but these are all instances of the principal type.
Example: These are all instances of 'a * 'b -> 'a
Initially, every type is free, i.e., an independent type variable. The inference process consists of applying constraints dictated by the program, which pins down the types just as much as required for the program to make sense.
Constraints
Constants have a precise monotype
3.0:real,4:int,true:bool,"abc":string
Certain built-in operators (but not all!) have a precise monotype.
(op div):int * int->int, chr: int -> string
Tuple constructor applications () always produce a tuple type, but don't dictate
the types in the tuple.
List constructor applications nil, ::, [] always result in a list type, and
force the elements of the list to be the same type, but don't dictate what
that type is.
Each arm of a case expression must evaluate to the same type.
For let-bound functions, types of formal parameters are constrained
(only) by the way in which parameters are used in the function body. Types of actual
arguments to applications of these functions must be instances of the formal argument
types.
For functions passed as arguments, things are a little more restrictive;
more details later.
Examples for Type inference
Overloading
The arithmetic operators +,-,*, apply to both real and int types, and the relational operators <,<=,>=,> apply to real, int, and string types. But these operators are not polymorphic, because they do something different to their arguments depending on type. Instead, we say that they are overloaded.
When these operators are used in a program, SML insists that it be possible to figure out the precise base type to which they are being applied. Often this can be determined automatically (e.g., sum). But sometimes it is necessary to give an explicit type constraint to clarify which version of the operator is needed.e.g.:
There is no provision for adding user-defined overloaded operators in SML.
Equality
The equality operators (=,<>) are even more special. They are almost polymorphic: they define structural (i.e., component-wise) equality on almost any type except on function types. So we can write a function like:
But we mustn't apply it like this:
The special equality type variables ''a,''b, can only be
instantiated to types that don't contain an arrow.