Types and type checking
Types have no role at run time (Type declarations); they only feed the type checker, which reports mismatches ahead of time. What the checker does beyond the signature syntax itself is polymorphism, overloading and the treatment of undeclared names.
Polymorphic predicates
An uppercase name in a signature is a type variable, universally quantified. A signature is inferred, never explicitly instantiated, so the same predicate works at every type its rules type-check at:
../code/polymorphic.elpi:
1% A polymorphic signature: the same predicate works at list int and at
2% list string, its type variable A instantiated differently at each call.
3
4pred rev list A -> list A.
5rev L R :- std.rev L R.
6
7main :- rev [1, 2, 3] R1, rev ["a", "b"] R2, print R1 R2.
[3, 2, 1] [b, a]
R1 is instantiated at list int, R2 at list string, from the
very same rev. This only happens because the signature spells out the type
variable A explicitly: the checker does not infer that a predicate is
polymorphic the way, say, OCaml’s let-polymorphism would. A predicate
with no signature at all is checked at whatever concrete types its call sites
happen to use, and a mismatch between two call sites is then a type error.
Overloading
Repeating a signature with different argument types overloads the predicate: the checker keeps every alternative and, at each use, picks the one the arguments fit:
pred px int -> string.
pred px bool -> string.
px 0 "zero".
px tt "true".
px (N : int) "nonzero" :- N > 0.
0 and tt pin their rules to one signature each. The third rule’s
N does not, since an integer or a boolean would type-check, so it
carries a type ascription (N : int) (Terms). A call
with an unknown argument in a position where the signatures differ needs one
the same way: px X S is ambiguous, px (X : int) S is not.
Undeclared constants
A constant that is used but never given a signature draws a warning naming a
signature that would fit, and the program still runs. A data constructor,
though, is a hard error the moment it is used (Type declarations).
The warning is skipped for two names: the predicate called main, and any
name ending in .aux or containing .aux., conventionally used for
generated or throwaway helper code that is not worth a signature.