A Prolog Specifiation of Extensible Records using Row Polymorphism

Ki Yung  Ahn

Part II (2016-11-29) Invited Talk VI @ CoAlpTy '16

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

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).
\frac{x:A\;\in\;C}{ C\;\vdash\;x\;:\;A}
$\frac{x:A\;\in\;C}{ C\;\vdash\;x\;:\;A}$
\frac{C,\,x:A\;\vdash\;e\,:\,B}{ C\;\vdash\;\lambda x.e\;:\;A\to B}
$\frac{C,\,x:A\;\vdash\;e\,:\,B}{ C\;\vdash\;\lambda x.e\;:\;A\to B}$
\frac{ C\;\vdash\;e_1\,:\,A\to B \quad C\;\vdash\;e_2\,:\,A }{ C\;\vdash\;e_1\;e_2\;:\;B }
$\frac{ C\;\vdash\;e_1\,:\,A\to B \quad C\;\vdash\;e_2\,:\,A }{ C\;\vdash\;e_1\;e_2\;:\;B }$
:- 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)) .

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]
(1)~\forall X^{*}.X \to X
$(1)~\forall X^{*}.X \to X$
(2)~\forall X^{*}.\forall Y^{*}.\forall F^{*\to*}.(X \to Y) \to F\,X \to F\,Y
$(2)~\forall X^{*}.\forall Y^{*}.\forall F^{*\to*}.(X \to Y) \to F\,X \to F\,Y$

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

\begin{array}{ll} f &:~ \forall R,~\{x:t_1\}\cup R \to t_1' \\ f(s) &~=~ ...~~ s.x ~~... \\ \\ g &:~ \forall R,~\{y:t_2\}\cup R \to t_2' \\ g(s) &~=~ ...~~ s.y ~~... \\ \\ h &:~ \forall R,~\{x:t_1,\,y:t_2\}\cup R \to t \\ h(s) &~=~ ...~~ f(s) ~~...~~ g(s) ~~... \end{array}
$\begin{array}{ll} f &:~ \forall R,~\{x:t_1\}\cup R \to t_1' \\ f(s) &~=~ ...~~ s.x ~~... \\ \\ g &:~ \forall R,~\{y:t_2\}\cup R \to t_2' \\ g(s) &~=~ ...~~ s.y ~~... \\ \\ h &:~ \forall R,~\{x:t_1,\,y:t_2\}\cup R \to t \\ h(s) &~=~ ...~~ f(s) ~~...~~ g(s) ~~... \end{array}$

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}).
\kappa ::= x \mid \kappa\to\kappa \mid \mathtt{o} \mid \mathtt{row}
$\kappa ::= x \mid \kappa\to\kappa \mid \mathtt{o} \mid \mathtt{row}$

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,_,_,_|_],  ... ...

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
• User defined equality and Open structure unification are not very compatible with Prolog's default search
• don't have any fancy theory
• don't have practical tool for production use

Comparison to Constraint Generation & Solving

• Constraint Generation & Solving (two stages)
1. C gen:        program -----> c1, c2, ..., cn
2. 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.
• 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)

• 470