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.

tex2html_wrap_inline114 Polymorphism allows us to write functions that work uniformly on container data structures without regard for the contained type.

tex2html_wrap_inline114 Higher-order functions allow us to define functions that share a uniform control structure as instances of a single function.


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, tex2html_wrap_inline118 , pronounced ``alpha, beta, tex2html_wrap_inline118 ''.

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.


tex2html_wrap_inline114 Constants have a precise monotype


tex2html_wrap_inline114 Certain built-in operators (but not all!) have a precise monotype.

(op div):int * int->int, chr: int -> string

tex2html_wrap_inline114 Tuple constructor applications () always produce a tuple type, but don't dictate the types in the tuple.

tex2html_wrap_inline114 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.

tex2html_wrap_inline114 Each arm of a case expression must evaluate to the same type.

tex2html_wrap_inline114 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.

tex2html_wrap_inline114 For functions passed as arguments, things are a little more restrictive; more details later.

Examples for Type inference



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.


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, tex2html_wrap_inline118 can only be instantiated to types that don't contain an arrow.

Andrew P. Tolmach
Tue Apr 15 16:26:05 PDT 1997