A Prolog Specifiation of Extensible Records
using Row Polymorphism
Part II (2016-11-29) Invited Talk VI @ CoAlpTy '16
Workshop on Coalgebra, Horn Clause Logic Programming and Types
Edinburgh, UK, 28–29 November 2016
Nanyang Technological University, Singapore
This slide is available online at https://slides.com/kyagrd/rowpoly-coalpty16
Outline
- Motivations and Goals
- Review: from STLC to Higher-Kinded Polymorphism
- Extensible Records using Row Polymorphism
- Discussions
Motivation
implementing type systems with rich features
including type inference
requires a lot of effort
Not enough generic tools for
deriving type inference engines
(c.f. Yacc for parser generation)
Logic Programs as Specifications of Type Systems
- Clauses are declarative specifications of typing rules
- Executable in both ways (type checking and inference)
- Native support for Unification (basic building block)
- STLC and HM can be quite naturally specified in Prolog
- More sophisiticated systems need more involved tweaks
Goal : LP Language for
Type System Specification
-
[Ongoing Work]
Case studies on type systems- try implementing them as declarative as possible in Prolog
- identify features that could express them more naturally
-
[Future Work]
Design a LP language (or eDSL) supporting those features- a generic framework for deriving type system implementations from declarative specifications
Example Prolog Codes
Outline
- Motivations and Goals
- Review: from STLC to Higher-Kinded Polymorphism
- Extensible Records using Row Polymorphism
- Discussions
Simply-Typed Lambda Calculus
:- set_prolog_flag(occurs_check,true).
:- op(500,yfx,$).
type(C,var(X), T) :- first(X:T,C).
type(C,lam(X,E),A -> B) :- type([X:A|C], E, B).
type(C,E1 $ E2, B) :- type(C,E1,A->B),
type(C,E2,A).
first(K:V,[K1:V1|Xs]) :- K = K1, V = V1.
first(K:V,[K1:V1|Xs]) :- K\==K1, first(K:V, Xs).
:- set_prolog_flag(occurs_check,true).
:- op(500,yfx,$).
type(C,var(X), T) :- first(X:T,C).
type(C,lam(X,E),A -> B) :- type([X:A|C], E, B).
type(C,E1 $ E2, B) :- type(C,E1,A->B),
type(C,E2,A).
first(K:V,[K1:V1|Xs]) :- K = K1, V = V1.
first(K:V,[K1:V1|Xs]) :- K\==K1, first(K:V, Xs).
?- type([], lam(x,var(x)), A->A). % type checking true . ?- type([], lam(x,var(x)), T). % type inference T = (_G123->_G123) . ?- type([], E, A->A). % type inhabitation E = lam(_G234,var(_G234)) .
Simply-Typed Lambda Calculus
HM = STLC + Type Poly.
type(C,var(X), T1) :- first(X:T,C), inst(T,T1).
type(C,lam(X,E), A -> B) :- type([X:mono(A)|C],E,B).
type(C,E1 $ E2, B ) :- type(C,E1,A -> B),
type(C,E2,A).
type(C,let(X=E0,E1), T ) :- type(C,E0,A),
type([X:poly(C,A)|C],E1,T).
inst(mono(T),T).
inst(poly(C,T),T1) :- copy_term(t(C,T),t(C,T1)).
- Type binding X:A in STLC corresponds to X:mono(A) in HM
- poly(C,A) is a type scheme of A closed under the context C
- Instantiation implemented by Prolog's built-in copy_term
HM = STLC + Type Poly.
type(C,var(X), T1) :- first(X:T,C), inst(T,T1).
type(C,lam(X,E), A -> B) :- type([X:mono(A)|C],E,B).
type(C,E1 $ E2, B ) :- type(C,E1,A -> B), type(C,E2,A).
type(C,let(X=E0,E1), T ) :- type(C,E0,A),
type([X:poly(C,A)|C],E1,T).
inst(mono(T),T).
inst(poly(C,T),T1) :- copy_term(t(C,T),t(C,T1)).
?- copy_term(t([],A->B), t([],T)). T = (_G993->_G994). % fresh vars: _G993, _G994 for A, B ?- copy_term(t([x:A],A->B), t([x:A],T)). T = (A->_G1024). % fresh vars: _G1024 for B only
Type Constructor Polymorphism
(a.k.a. higher-kinded polymorphism)
-- Tree :: (* -> *) -> * -> *
data Tree c a
= Leaf a -- Leaf :: Tree c a
| Node (c (Tree c a)) -- Node :: (c(Tree c a)) -> Tree c a
type BinTree a = Tree Pair a -- two children on each node
newtype Pair t = Pair (t,t)
type RoseTree a = Tree List a -- varying number of children
newtype List a = List [a]
HM only supports type polymorphism such as (1) but
not higher-kinded poly. such as (2) supported in Haskell
HM + TyCon Poly
:- set_prolog_flag(occurs_check,true).
:- op(500,yfx,$).
first(K:V,[K1:V1|_]) :- K = K1, V = V1.
first(K:V,[K1:_|Zs]) :- K\==K1, first(K:V, Zs).
kind(KC,var(Z), K) :- first(Z:K,KC).
kind(KC,F $ G, K2) :- kind(KC,F,K1->K2), kind(KC,G,K1).
kind(KC,A -> B, o) :- kind(KC,A,o), kind(KC,B,o).
type(KC,C,var(X), A) --> { first(X:S,C) }, inst_ty(KC,S,A).
type(KC,C,lam(X,E),A->B) --> type(KC,[X:mono(A)|C],E,B),
[ kind(KC,A->B,o) ].
type(KC,C,E1 $ E2, B) --> type(KC,C,E1,A->B), type(KC,C,E2,A).
type(KC,C,let(X=E0,E),T) --> type(KC,C,E0,A),
type(KC,[X:poly(C,A)|C],E,T).
inst_ty(KC,poly(C,T),T2) --> { copy_term(t(C,T),t(C,T1)),
free_variables(T,Xs),
free_variables(T1,Xs1) },
samekinds(KC,Xs,Xs1), { T1=T2 }.
inst_ty(_, mono(T), T) --> [].
samekinds(KC,[X|Xs],[Y|Ys]) --> { X\==Y },
[ kind(KC,X,K), kind(KC,Y,K) ],
samekinds(KC,Xs,Ys).
samekinds(KC,[X|Xs],[X|Ys]) --> [], samekinds(KC,Xs,Ys).
samekinds(_ ,[], [] ) --> [].
variablize(var(X)) :- gensym(t,X).
main:-
process,
halt.
type_and_print(KC,C,E,T) :-
phrase(type(KC,C,E,T),Gs), print(success_type), nl,
(bagof(Ty, X^Y^member(kind(X,Ty,Y),Gs), Tys); Tys = []),
free_variables(Tys,Xs), maplist(variablize,Xs),
maplist(call,Gs),
write("kind ctx instantiated as: "), print(KC), nl, print(E : T), nl.
process:-
type_and_print(_,[],lam(x,var(x)),_), nl,
type_and_print(_,[],lam(x,lam(y,var(y)$var(x))),_), nl,
type_and_print(_,[],let(id=lam(x,var(x)),var(id)$var(id)),_), nl,
KC0 = [ 'Nat':o, 'List':(o->o) | _ ],
Nat = var('Nat'), List = var('List'),
C0 = [ 'Zero':mono(Nat)
, 'Succ':mono(Nat -> Nat)
, 'Nil' :poly([], List$A)
, 'Cons':poly([], A->((List$A)->(List$A))) ],
type_and_print(KC0,C0,lam(x,lam(n,var(x)$var('Succ')$var(n))),_),
true.
:- main.
HM + TyCon Poly
type(KC,C,lam(X,E),A->B) --> type(KC,[X:mono(A)|C],E,B),
[ kind(KC,A->B,o) ].
type(KC, [], lam(x,var(x)), T)
--> {A->B/T}
type(KC,[x:mono(A)|[]],var(x),B), [ kind(KC,A->B,o) ]
--> ... --> {A/B}
type(KC,[x:mono(A)|[]),var(x),A), [ kind(KC,A->A,o) ]
% make logic var A into concrete var var(t1) befor kind infer
kind(KC, var(t1)->var(t1),o)
kind([t1:o|_], var(t1)->var(t1),o)
Extra-logical features used
- 'first' predicate implemented using \==
- 'copy_term' for implementing type instantiation
- Definite Clause Grammars (DCGs) to delay 'kind' goals
- 'free_variables'
- 'gensym'
A few more things
Ki Yung Ahn, Andrea Vezzosi: Executable Relational Specifications of Polymorphic Type Systems Using Prolog. FLOPS 2016: 109-125
- kind polymorphism is easy to add to these rules
- HM on the type level (do instatiation in 'kind')
- recursive functions, pattern matching, ...
Outline
- Motivations and Goals
- Review: from STLC to Higher-Kinded Polymorphism
- Extensible Records using Row Polymorphism
- Discussions
Row Polymorphism
Extensible Records without Subtyping
HM + TyCon Poly + Row Poly
kind(KC,var(X), K1) :- first(X:K,KC).
kind(KC,F $ G, K2) :- K2\==row, kind(KC,F,K1->K2),
K1\==row, kind(KC,G,K1).
kind(KC,A -> B, o) :- kind(KC,A,o), kind(KC,B,o).
kind(KC,{R}, o) :- kind(KC,R,row).
kind(KC,[], row).
kind(KC,[X:T|R], row) :- kind(KC,T,o), kind(KC,R,row).
type(KC,C,var(X), A) --> { first(X:S,C) }, inst_ty(KC,S,A).
type(KC,C,lam(X,E),A->B) --> type(KC,[X:mono(A)|C],E,B),
[ kind(KC,A->B,o) ].
type(KC,C,X $ Y, B) --> type(KC,C,X,A->B), type(KC,C,Y,A1),
!, { eqty(A,A1) }. % note the cut !
type(KC,C,let(X=E0,E),T) --> type(KC,C,E0,A),
type(KC,[X:poly(C,A)|C],E,T).
type(KC,C,{XEs}, {R}) --> { zip_with('=',Xs,Es,XEs) },
type_many(KC,C,Es,Ts),
{ zip_with(':',Xs,Ts,R) }.
type(KC,C,sel(L,X), T) --> { first(X:T,R) }, type(KC,C,L,{R}).
HM + TyCon Poly + Row Poly
type(KC,C,X $ Y, B) --> type(KC,C,X,A->B), type(KC,C,Y,A1),
!, { eqty(A,A1) }. % note the cut !
% more advanced notion of type equality at work
eqty(A1,A2) :- (var(A1); var(A2)), !, A1=A2.
eqty({R1},{R2}) :- !, unify_oemap(R1,R2). % like a permutation
eqty(A1->B1,A2->B2) :- !, eqty(A2,A1), !, eqty(B1,B2).
eqty(A,A).
% unify open ended maps (possibly variable tail at the end)
unify_oemap(A,B) :- ( var(A); var(B) ), !, A=B.
unify_oemap(A,B) :-
split_heads(A,Xs-T1), make_map(Xs,M1), % M1 is closed map
split_heads(B,Ys-T2), make_map(Ys,M2), % M2 is closed map
unify_oe_map(M1-T1, M2-T2). % calls eqty (mutual recursion)
generalization of open-ended set unification for maps and user defined equality
- Frieder Stolzenburg (1996). Membership-Constraints and Complexity in Logic Programming with Sets, Volume 3, Applied Logic Series, pp 285-302
- Unification over open-ended colections using Membershp-Constraints has been studied
- for non-nested collections
- as an independent query
- A few more complications for row-polymorphism like type system implementation (nested collections, user defined equality, used with other queries)
- the default Prolog search strategy and unification did not have open-ended structures in mind
- needs to be guarded by cut (!) because
when [] = [x:T|_] fails Prolog tries
[] = [x:T,_|_], [] = [x:T,_,_|_], [] = [x:T,_,_,_|_], ... ...
- needs to be guarded by cut (!) because
Issues of Row Poly in Prolog
Outline
- Motivations and Goals
- Review: from STLC to Higher-Kinded Polymorphism
- Extensible Records using Row Polymorphism
- Discussions
Summary
- Logic Programming seems to be very promising for declarative implementation of polymorphic features
- HM, Type Constructor Poly, Kind Poly, Row Poly
- Identified what Prolog doesn't work well for executable relational specifications of type systems
- Logic Var vs. Concrete Names at differrent levels
- Want type vars as logic vars in 'type' inference
but concrete names in 'kind' inference
- Want type vars as logic vars in 'type' inference
- User defined equality and Open structure unification are not very compatible with Prolog's default search
- Logic Var vs. Concrete Names at differrent levels
- don't have any fancy theory
- don't have practical tool for production use
Comparison to
Constraint Generation & Solving
- Constraint Generation & Solving (two stages)
- C gen: program -----> c1, c2, ..., cn
- C solver: c1, c2, ... cn -----> yes/no
- Here we generate and solve on the fly while executing the logic program. Some of constraint generation for advanced features involves meta-programming (e.g., copy_term, free_variables)
Future Work
- Try this Prolog experment for First-Class Polymorphism
- C. V. Russo and D. Vytiniotis (2009).
QML: Explicit First-Class Polymorphism for ML.
- C. V. Russo and D. Vytiniotis (2009).
- Make open structure unification less control sensitive
- search strategy other than Prolog's default
- Better abstraction for delaying goals and switching from logic vars to concrete names accross different stages of inference
- ... ... ...
A Prolog Specifiation of Extensible Records
By 안기영 (Ahn, Ki Yung)
A Prolog Specifiation of Extensible Records
- 3,469