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).
\frac{x:A\;\in\;C}{ C\;\vdash\;x\;:\;A}
x:ACCx: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}
C,x:Ae:BCλx.e:AB\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 }
Ce1:ABCe2:ACe1e2: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)) .

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]
(1)~\forall X^{*}.X \to X
(1) X.XX(1)~\forall X^{*}.X \to X
(2)~\forall X^{*}.\forall Y^{*}.\forall F^{*\to*}.(X \to Y) \to F\,X \to F\,Y
(2) X.Y.F.(XY)FXFY(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}
f: R, {x:t1}Rt1f(s) = ...  s.x  ...g: R, {y:t2}Rt2g(s) = ...  s.y  ...h: R, {x:t1,y:t2}Rth(s) = ...  f(s)  ...  g(s)  ...\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}
κ::=xκκorow\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,_,_,_|_],  ... ...

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
    • 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)

A Prolog Specifiation of Extensible Records

  • 3,255