Logical Foundations - Polymorphism and Higher-Order Functions
Posted by Hux on January 4, 2019
The critical new ideas are
polymorphism (abstracting functions over the types of the data they manipulate) and
higher-order functions (treating functions as data).
Polymorphism
Until today, We were living in the monomorphic world of Coq.
So if we want a list, we have to define it for each type:
But of course Coq supports polymorphic type.
So we can abstract things over type
1
2
3
4
5
6
Inductivelist(X:Type):Type:=|nil|cons(x:X)(l:listX).Checklist.(* ===> list : Type -> Type *)
Recall from PLT course, this is exacly Parametric Polymorphism
and it’s SystemFω. the list here is a type-level small lambda, or type operators
Another things I’d love to mention is the concrete syntax of list X,
it didn’t choose the SML/OCaml order but the Haskell order.
Q1. What’s the type of nil and cons?
Both having forall type
1
2
3
4
Checknil.(* ===> nil : forall X : Type, list X *)Checkcons.(* ===> nil : forall X : Type, X -> list X -> list X *)
Q2. What’s the type of list nat? Why not Type but weird Set?
1
2
3
4
5
6
7
8
Checknat.(* ===> nat : Set *)Checklistnat.(* ===> list nat : Set *)CheckSet.(* ===> Set: Type *)CheckType.(* ===> Type: Type *)
1
Check(consnat2(consnat1(nilnat))).
Polymorphic Functions
we can make polymorphic versions of list-processing function:
Btw, Pierce follows the TAPL convention where type is written in capital letter but not greek letter,
less clear in first look but better for typing in real programming.
Check repeat.
(* ===> repeat : forall X : Type, X -> nat -> list X *)
Slide QA
ill-typed
forall X : Type, X -> nat -> list X
list nat
Type Argument Inference
X must be a Type since nil expects an Type as its first argument.
1
2
3
4
5
6
7
8
Fixpointrepeat'Xxcount:listX:=(* return type [:list X] can be omitted as well *)matchcountwith|0⇒nilX|Scount'⇒consXx(repeat'Xxcount')end.Checkrepeat'.(* ===> forall X : Type, X -> nat -> list X *)
Type Argument Synthesis
We can write _ (hole) in place of X and Coq will try to unify all local information.
Doing this will make X implicit for even list', the type constructor itself…
Other Polymorphic List functions
No difference but add implicit type argument {X : Type}.
Supplying Type Arguments Explicitly
1
2
3
4
5
6
FailDefinitionmynil:=nil.Definitionmynil:listnat:=nil.Check@nil.(* ===> @nil : forall X : Type, list X *)Definitionmynil':=@nilnat.
First thought: Existential
Second thought: A wait to be unified Universal. (after being implicit and require inference)
1
2
3
4
5
Checknil.nil:list?Xwhere?X:[|-Type]
List notation
1
2
3
4
5
6
Notation"x :: y":=(consxy)(atlevel60,rightassociativity).Notation"[ ]":=nil.Notation"[ x ; .. ; y ]":=(consx..(consy[])..).Notation"x ++ y":=(appxy)(atlevel60,rightassociativity).
Same with before thanks to the implicit argument
Slide Q&A 2
we use ; not ,!!
list nat
ill-typed
ill-typed
list (list nat)
list (list nat) (tricky in first look)
list bool
ill-typed
ill-typed
Poly Pair
1
2
3
4
5
6
Inductiveprod(XY:Type):Type:=|pair(x:X)(y:Y).Argumentspair{X}{Y}__.(* omit two type var **)Notation"( x , y )":=(pairxy).Notation"X * Y":=(prodXY):type_scope.(* only be used when parsing type, avoids clashing with multiplication *)
Be careful of (X,Y) and X*Y. Coq pick the ML way, not haskell way.
Check@combine.@combine:forallXY:Type,listX->listY->list(X*Y)(* A special form of `forall`? *)Checkcombine.combine:list?X->list?Y->list(?X*?Y)where?X:[|-Type]?Y:[|-Type]
Poly Option
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Inductiveoption(X:Type):Type:=|Some(x:X)|None.ArgumentsSome{X}_.ArgumentsNone{X}.(* find nth element if exist, None otherwise *)Fixpointnth_error{X:Type}(l:listX)(n:nat):optionX:=matchlwith|[]⇒None|a::l'⇒ifn=?OthenSomeaelsenth_errorl'(predn)end.
Function as data
Functions as first-class citizens
Higher-Order Functions
1
2
3
4
5
Definitiondoit3times{X:Type}(f:X→X)(n:X):X:=f(f(fn)).Check@doit3times.(* ===> doit3times : forall X : Type, (X -> X) -> X -> X *)
Filter (taking a predicate on X)
collection-oriented programming style - my first time seeing this, any comments?
It is arguably a little sad, in the example just above, to be forced to define the function length_is_1 and give it a name just to be able to pass it as an argument to filter
I encounter this problem when doing church numeral exercise.
1
Definitionplus(nm:cnat):cnat:=ncnatsuccm.
will result in universe inconsistency error.
1
2
3
4
5
6
7
SetPrintingUniverses.(* giving more error msg *)Inenvironmentn:cnatm:cnatTheterm"cnat"hastype"Type@{Top.168+1}"whileitisexpectedtohavetype"Type@{Top.168}"(universeinconsistency:CannotenforceTop.168<Top.168becauseTop.168=Top.168).
What’s happening?
Yes, you can define: Definition plus (n m : cnat) : cnat := n cnat succ m. in System F. However, in Coq’s richer logic, you need to be a little more careful about allowing types to be instantiated at their own types, else you run into issue of inconsistency. Essentially, there is a stratification of types (by “universes”) that says that one universe cannot contain a “bigger” universe. Often, things are polymorphic in their universe (i.e., work in all universes), you run into this where you cannot instantiate the “forall X, …” that is the definition of cnat by cnat itself.
– Prof. Fluet
Thus, the correct answer for that question is that Type_i has type Type_j, for any index j > i. This is needed to ensure the consistency of Coq’s theory: if there were only one Type, it would be possible to show a contradiction, similarly to how one gets a contradiction in set theory if you assume that there is a set of all sets.
Coq generates one new index variable every time you write Type, and keeps track of internal constraints
The error message you saw means that Coq’s constraint solver for universe levels says that there can’t be a solution to the constraint system you asked for.
The problem is that the forall in the definition of nat is quantified over Type_i, but Coq’s logic forces nat to be itself of type Type_j, with j > i. On the other hand, the application n nat requires that j <= i, resulting in a non-satisfiable set of index constraints.
Thecommandhasindeedfailedwithmessage:Theterm"@identity"hastype"forall A : Type, A -> A"whileitisexpectedtohavetype"?A"(unabletofindawell-typedinstantiationfor"?A":cannotensurethat"Type@{Top.1+1}"isasubtypeof"Type@{Top.1}").
The link also introduce some advanced/experimental way to do polymorphic universe
Polymorphic Church Numerals w/o self-applying itself
Untyped doesn’t need to declare type…
STLC doesn’t have enough expressive power to represent church encoding
System F definition:
1
Definitioncnat:=forallX:Type,(X->X)->X->X.
succ
1
succ=\nsz->s(nsz)
1
2
Definitionsucc(n:cnat):cnat:=funXsz=>s(nXsz).
plus
1
2
plus=\mn->msccnplus=\mnsz->ms(nsz)
1
2
3
Definitionplus(nm:cnat):cnat:=ncnatsuccm.(* System F *)funXsz=>nXs(mXsz).(* Coq *)
plus =
lambda m:CNat.
lambda n:CNat. (
lambda X.
lambda s:X->X.
lambda z:X.
m [X] s (n [X] s z)
) as CNat;
plus =
lambda m:CNat.
lambda n:CNat.
m [CNat] succ' n;