Structural Types, Recursive Modules, and the Expression Problem Jacques Garrigue Nagoya University, Grad. Sch. of Mathematics
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 1 Synopsis – The Expression Problem – Polymorphic variant solution – Recursive modules – Private row types – Solution using recursive modules and private row types – Other applications of private row types
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 2 The Expression Problem Extending a language of expressions, by adding both new cases and new operations, without recompilation, but with static safety. Studied in the 1990s by Cook, Felleisen, ..., formalized by Wadler. – adding new cases is easy with OO, but hard with algebraic datatypes – adding new operations is easy with algebraic datatypes, but hard with OO It looks like an expressiveness benchmark for object-oriented languages, but functional programming languages might actually be better.
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 3 Flasback to WG2.8 2000 I proposed a solution based on polymorphic variants and a delayed fixpoint. type ’a eplus = [‘Num of int | ‘Plus of ’a * ’a] let eval_plus eval_rec : ’a eplus -> int = function ‘Num n -> n | ‘Plus(e1,e2) -> eval_rec e1 + eval_rec e2 val eval_plus : (’a -> int) -> ’a eplus -> int let rec eval1 e = eval_plus eval1 e val eval1 : (’a eplus as ’a) -> int eval1 (‘Plus(‘Num 3, ‘Num 4)) - : int = 7 This requires recursion at both type and value level.
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 4 Solution (cont.) Extension uses special “expanded” patterns. type ’a emult = [’a eplus | ‘Mult of ’a * ’a] let eval_mult eval_rec : ’a emult -> int = function [ ‘Mult of ’a * ’a | ‘Num of int | ‘Plus of ’a * ’a ] #eplus as e -> eval_plus eval_rec e | ‘Mult(e1,e2) -> eval_rec e1 * eval_rec e2 (‘Num | ‘Plus ) val eval_mult : (’a -> int) -> ’a emult -> int let rec eval2 e = eval_mult eval2 e val eval2 : (’a emult as ’a) -> int eval2 (‘Plus(‘Mult(‘Num 3,‘Num 4), ‘Num 5)) - : int = 17
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 5 Advanced operation: simplification Simplification illustrates the need to pattern-match on unknown types, and to produce values of these types. let simp_plus simp_rec : ’a eplus -> ([> ’a eplus] as ’a) = function ‘Num _ as e -> e means [‘Num | ‘Plus | ..] | ‘Plus(e1,e2) -> match simp_rec e1, simp_rec e2 with ‘Num m, ‘Num n -> ‘Num (m+n) | e12 -> ‘Plus e12 val simp_plus : (([> ’a eplus ] as ’a) -> ’a) -> ’a eplus -> ’a let rec simp1 e = simp_plus simp1 e val simp1 : (’a eplus as ’a) -> ’a simp1 (‘Plus(‘Num 3, ‘Num 4)) - : ’a eplus as ’a = ‘Num 7
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 6 A good first solution – No simple object-oriented solution at that time – Shorter than any solution presented later – Allows pattern-matching Yet, – Relies heavily on type inference – Not very scalable
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 7 Hard to solve in core language type (’a,’b) ops = { eval: ’a -> int; simp: ’a -> ’b } let ops_plus ops : (’a eplus, [> ’a eplus] as ’a) ops = { eval = (function ‘Num n -> n | ‘Plus(e1,e2) -> ops.eval e1 + ops.eval e2); simp = ... } val ops_plus : ([> ’a eplus ] as ’a, ’a) ops -> (’a eplus, ’a) ops let rec plus = ops_plus plus This kind of expression is not allowed as right-hand side of ‘let rec’ – value-recursion problem – still relies heavily on type inference
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 8 Hard to solve in core language type (’a,’b) ops = { eval: ’a -> int; simp: ’a -> ’b } let ops_plus ops : (’a eplus, [> ’a eplus] as ’a) ops = { eval = (function ‘Num n -> n | ‘Plus(e1,e2) -> ops.eval e1 + ops.eval e2); simp = ... } val ops_plus : ([> ’a eplus ] as ’a, ’a) ops -> (’a eplus, ’a) ops let lazy_ops ops = { eval = (fun x -> (Lazy.force ops).eval x); simp = (fun x -> (Lazy.force ops).simp x) } val lazy_ops : (’a, ’b) ops Lazy.t -> (’a, ’b) ops let rec lazy_plus = lazy (ops_plus (lazy_ops lazy_plus)) val lazy_plus : (’a eplus as ’a, ’a) ops Lazy.t let plus = Lazy.force lazy_plus val plus : (’a eplus as ’a, ’a) ops
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 9 Scalable fixpoint = Recursive module Recursive modules seem the way to go, as they provide – fixpoints of functors, with – scalability (simultaneous parameterization by multiple types and functions) – explicit typing (through signatures) But with no abstraction on type structure, this can be rather unwieldly.
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 10 Modules without private rows (1/2) module PlusT(T : ET) = struct (* Types involved in our recursion *) type exp = [‘Num of int | ‘Plus of T.exp * T.exp] module type ET = sig type exp end end (* Recursive operations on our types *) module Plus(E : E)(C : CW(E.T)(PlusT(E.T)).C) = module type E = sig struct module T : ET module T = PlusT(E.T) val eval : T.exp -> int let eval : T.exp -> int = function val simp : T.exp -> T.exp ‘Num n -> n end | ‘Plus(e1,e2) -> E.eval e1 + E.eval e2 (* A functor building the type of the let simp : T.exp -> E.T.exp = function conversion module between two types *) ‘Num _ as e -> C.inj e module CW(U : ET)(L : ET) = struct E.T.exp | ‘Plus(e1,e2) -> module type C = sig let e1 = E.simp e1 and e2 = E.simp e2 in val inj : L.exp -> U.exp match C.proj e1, C.proj e2 with val proj : U.exp -> L.exp option Some(‘Num m), Some(‘Num n) -> end C.inj (‘Num(m+n)) end | _ -> C.inj (‘Plus(e1,e2)) (* The identity conversion module *) end module CI = struct module rec PlusF : E with module T = PlusT(PlusF.T) let inj x = x = Plus(PlusF)(CI) let proj x = Some x let e1 = PlusF.simp (‘Plus(‘Num 3, ‘Num 4)) end val e1 : PlusF.T.exp = ‘Num 7
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 11 Modules without private rows (2/2) let eval : T.exp -> int = function (* The Mult language *) #LPlus.T.exp as e -> LPlus.eval e | ‘Mult(e1,e2) -> E.eval e1 * E.eval e2 module MultT(T : ET) = struct let simp : T.exp -> E.T.exp = function type exp = #LPlus.T.exp as e -> LPlus.simp e [PlusT(T).exp | ‘Mult of T.exp * T.exp] | ‘Mult(e1,e2) -> end let e1 = E.simp e1 module Mult(E : E)(C : CW(E.T)(MultT(E.T)).C) = and e2 = E.simp e2 in struct match C.proj e1, C.proj e2 with module T = MultT(E.T) Some(‘Num m), Some(‘Num n) -> module CPlus = struct C.inj (‘Num(m*n)) type t = PlusT(E.T).exp | _ -> C.inj (‘Plus(e1,e2)) (* All conversion modules use the same end code, but we need to know the concrete module rec MultF types to build our coercions *) : E with module T = MultT(MultF.T) let inj = (C.inj :> t -> _) = Mult(MultF)(CI) let proj x = match C.proj x with Some #t as y -> y let e2 = MultF.simp | _ -> None (‘Plus(‘Mult(‘Num 3,‘Num 4), ‘Num 5)) end val e2 : MultF.T.exp = ‘Num 17 module LPlus = Plus(E)(CPlus)
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 12 Private row types Object or variant types whose “row-variable” is kept abstract. type basic = [‘Int of int | ‘String of string] module Prop(X : sig type t = private [> basic] end) = struct let empty : X.t = ‘String "" let to_string (v : X.t) = match v with ‘Int n -> string_of_int n | ‘String s -> s | _ -> "other" (* required by the abstract row *) end type extra = [basic | ‘Bool of bool] module MyProp = Prop(struct type t = extra end) module MyProp : sig val empty : extra val to_string : extra -> string end
Jacques Garrigue — Structural Types, Recursive Modules, and the Expression Problem 13 What are private rows? Assume an over-simplified model of structural polymorphism, where the polymorphism is expressed by a row variable. [ ‘Int of int | ‘String of string | ’a ] Then a private row type is just a pair of type declarations type t_row type t = [ ‘Int of int | ‘String of string | t_row ] and we can just use the normal behaviour of functors. In practice however, row variables would not be sufficient to expressed all the forms of structural polymorphism we support. The real formalization uses kinds rather than rows, and some non trivial modifications are needed.
Recommend
More recommend