Skip to content

Commit f323f28

Browse files
committed
hmx_tc: HM(X) + multi parameter typeclasses
This extends `hmx` with multi parameter typeclasses. Follows THIH in general and adds an elaborator.
1 parent fecbc15 commit f323f28

18 files changed

+3024
-0
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222

2323
The elaboration mechanism is shamelessly stolen from [inferno][].
2424

25+
- `hmx_tc` - [HM(X)][] extends with Multi-Parameter Typeclasses (MPTC).
26+
Type inference and elaborator are implemented but the environment construction
27+
doesn't check for overlapping instances yet.
28+
2529
# Development
2630

2731
```

hmx_tc/bin/dune

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(executable
2+
(public_name hmx_tc)
3+
(name main)
4+
(libraries hmx_tc base stdio))

hmx_tc/bin/main.ml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
open Base
2+
open Hmx_tc
3+
4+
let () =
5+
let env =
6+
Env.empty
7+
|> Env.assume_val "one" "int"
8+
|> Env.assume_val "hello" "string"
9+
|> Env.assume_val "world" "string"
10+
|> Env.assume_val "pair" "a, b . (a, b) -> pair[a, b]"
11+
|> Env.assume_val "triple" "a, b, c . (a, b, c) -> triple[a, b, c]"
12+
|> Env.assume_val "quadruple"
13+
"a, b, c, e . (a, b, c, e) -> quadruple[a, b, c, e]"
14+
|> Env.assume_val "plus" "(int, int) -> int"
15+
|> Env.assume_val "true" "bool"
16+
|> Env.assume_val "nil" "a . list[a]"
17+
|> Env.assume_val "cons" "a . (a, list[a]) -> list[a]"
18+
(* eq *)
19+
|> Env.assume_tclass "eq" "a . (a, a) -> bool"
20+
|> Env.assume_tclass_instance "eq_int" "eq[int]"
21+
|> Env.assume_tclass_instance "eq_bool" "eq[bool]"
22+
|> Env.assume_tclass_instance "eq_list" "a . eq[a] => eq[list[a]]"
23+
|> Env.assume_tclass_instance "eq_pair"
24+
"a, b . eq[a], eq[b] => eq[pair[a, b]]"
25+
(* compare[a] *)
26+
|> Env.assume_tclass "compare" "a . eq[a] => (a, a) -> bool"
27+
|> Env.assume_tclass_instance "compare_list"
28+
"a . compare[a] => compare[list[a]]"
29+
|> Env.assume_tclass_instance "compare_int" "compare[int]"
30+
|> Env.assume_tclass_instance "compare_bool" "compare[bool]"
31+
(* coerce[a, b] *)
32+
|> Env.assume_tclass "coerce" "a, b . a -> b"
33+
|> Env.assume_tclass_instance "coerce_id" "a . coerce[a, a]"
34+
|> Env.assume_tclass_instance "coerce_list"
35+
"a, b . coerce[a, b] => coerce[list[a], list[b]]"
36+
|> Env.assume_tclass_instance "coerce_bool_int" "coerce[bool, int]"
37+
(* show *)
38+
|> Env.assume_tclass "show" "a . a -> string"
39+
|> Env.assume_tclass_instance "show_int" "show[int]"
40+
|> Env.assume_tclass_instance "show_float" "show[float]"
41+
(* (1* read *1) *)
42+
|> Env.assume_tclass "read" "a . string -> a"
43+
|> Env.assume_tclass_instance "read_int" "read[int]"
44+
|> Env.assume_tclass_instance "read_float" "read[float]"
45+
(* |> Env.assume_tclass "eq2" "a . (a, a) -> bool" *)
46+
(* |> Env.assume_tclass_instance "eq2_int" "eq2[int]" *)
47+
(* |> Env.assume_tclass_instance "eq2_list" "a . eq2[a] => eq2[list[a]]" *)
48+
(* |> Env.assume_val "ch" "a . compare[a], eq2[a] => (a, a) -> bool" *)
49+
in
50+
let e = Expr.parse_chan Stdio.stdin in
51+
match infer ~env e with
52+
| Ok e -> Caml.Format.printf "%s@." (Expr.show e)
53+
| Error err -> Caml.Format.printf "ERROR: %s@." (Type_error.show err)

hmx_tc/constraint.ml

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
(**
2+
3+
This module defines constraint language.
4+
5+
The constraint language has applicative structure (see [C_map] constructor)
6+
which is used for elaboration (the constraint solving algo computes a value).
7+
8+
*)
9+
10+
open Base
11+
open Syntax
12+
13+
type _ t =
14+
| C_trivial : unit t
15+
(** A trivial constraint, states nothing useful. Always can be solved. *)
16+
| C_eq : ty * ty -> unit t
17+
(** [C_eq (ty1, ty2)] states that the types [ty1] and [ty2] are equal. *)
18+
| C_inst : name * ty -> expr t
19+
(** [C_inst (name, ty)] states that [name] should be fetched from the
20+
environment, instantiated and equated to [ty]. *)
21+
| C_and : 'a t * 'b t -> ('a * 'b) t
22+
(** Conjuction of two constraints, possibly of different value type. *)
23+
| C_and_list : 'a t list -> 'a list t
24+
(** Conjuction of multiple constraints of the same value type. *)
25+
| C_exists : var list * 'a t -> 'a t
26+
(** [C_exists (vs, c)] existentially quantifies variables [vs] over [c]. *)
27+
| C_let :
28+
(name * var list * expr t * ty) list * 'b t
29+
-> ((expr * ty_sch) list * 'b) t
30+
(** [C_let (bindings, c)] works is a constraint abstraction fused with
31+
existential quantification. It adds [bindings] to the environment of
32+
the following constraint [c].
33+
34+
It allows to define multiple names at once to support n-ary functions. *)
35+
| C_map : 'a t * ('a -> 'b) -> 'b t
36+
(** Map operation, this gives an applicative structure. *)
37+
38+
let trivial = C_trivial
39+
40+
let return v = C_map (C_trivial, fun () -> v)
41+
42+
let ( =~ ) x y = C_eq (x, y)
43+
44+
let inst name cty = C_inst (name, cty)
45+
46+
let exists tys c =
47+
match tys with
48+
| [] -> c
49+
| tys -> (
50+
match c with
51+
| C_exists (tys', c) -> C_exists (tys @ tys', c)
52+
| c -> C_exists (tys, c))
53+
54+
let let_in bindings c = C_let (bindings, c)
55+
56+
let ( &~ ) x y = C_and (x, y)
57+
58+
let ( >>| ) c f = C_map (c, f)
59+
60+
let list cs = C_and_list cs
61+
62+
let rec layout' : type a. names:Names.t -> a t -> PPrint.document =
63+
fun ~names c ->
64+
let open PPrint in
65+
match c with
66+
| C_trivial -> string "TRUE"
67+
| C_eq (ty1, ty2) ->
68+
layout_ty' ~names ty1 ^^ string " = " ^^ layout_ty' ~names ty2
69+
| C_and (a, b) -> layout' ~names a ^^ string " & " ^^ layout' ~names b
70+
| C_and_list cs ->
71+
let sep = string " & " in
72+
separate sep (List.map cs ~f:(layout' ~names))
73+
| C_exists (vs, c) ->
74+
string ""
75+
^^ separate comma (List.map vs ~f:(layout_con_var' ~names))
76+
^^ dot
77+
^^ parens (layout' ~names c)
78+
| C_let (bindings, c) ->
79+
let layout_cty' : type a. a t * ty -> document = function
80+
| C_trivial, ty -> layout_ty' ~names ty
81+
| c, ty -> layout' ~names c ^^ string " => " ^^ layout_ty' ~names ty
82+
in
83+
let layout_binding : type a. string * var list * a t * ty -> document =
84+
fun (name, vs, c, ty) ->
85+
string name
86+
^^ string " : "
87+
^^
88+
match vs with
89+
| [] -> layout_cty' (c, ty)
90+
| vs ->
91+
let vs = layout_var_prenex' ~names vs in
92+
vs ^^ layout_cty' (c, ty)
93+
in
94+
let sep = comma ^^ blank 1 in
95+
string "let "
96+
^^ separate sep (List.map bindings ~f:layout_binding)
97+
^^ string " in "
98+
^^ layout' ~names c
99+
| C_inst (name, ty) -> string name ^^ string "" ^^ layout_ty' ~names ty
100+
| C_map (c, _f) -> layout' ~names c
101+
102+
include (
103+
struct
104+
type pack = P : _ t -> pack
105+
106+
include Showable (struct
107+
type t = pack
108+
109+
let layout (P c) =
110+
let names = Names.make () in
111+
layout' ~names c
112+
end)
113+
114+
let layout c = layout (P c)
115+
116+
let show c = show (P c)
117+
118+
let print ?label c = print ?label (P c)
119+
end :
120+
sig
121+
val layout : _ t -> PPrint.document
122+
123+
val show : _ t -> string
124+
125+
val print : ?label:string -> _ t -> unit
126+
end)

hmx_tc/debug.ml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
open! Base
2+
3+
let flags = Caml.Sys.getenv_opt "HMX_DEBUG" |> Option.value ~default:""
4+
5+
let log_levels = String.mem flags 'l'
6+
7+
let log_solve = String.mem flags 's'
8+
9+
let log_instantiate = String.mem flags 'i'
10+
11+
let log_generalize = String.mem flags 'g'
12+
13+
let log_unify = String.mem flags 'u'
14+
15+
let log_match = String.mem flags 'm'
16+
17+
let log_define = String.mem flags 'd'

hmx_tc/dune

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
(library
2+
(name hmx_tc)
3+
(preprocess (pps ppx_sexp_conv))
4+
(libraries base pprint nice_parser)
5+
)
6+
7+
(ocamllex lexer)
8+
9+
(menhir
10+
(modules parser)
11+
(flags --explain --dump))

hmx_tc/hmx_tc.ml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module Expr = struct
2+
include Syntax.Expr
3+
4+
include Nice_parser.Make (struct
5+
type token = Parser.token
6+
7+
type result = Syntax.expr
8+
9+
let parse = Parser.expr_eof
10+
11+
let next_token = Lexer.token
12+
13+
exception ParseError = Parser.Error
14+
15+
exception LexError = Lexer.Error
16+
end)
17+
end
18+
19+
module Ty = struct
20+
include Syntax.Ty
21+
end
22+
23+
module Ty_sch = struct
24+
include Syntax.Ty_sch
25+
26+
include Nice_parser.Make (struct
27+
type token = Parser.token
28+
29+
type result = Syntax.ty_sch
30+
31+
let parse = Parser.ty_sch_eof
32+
33+
let next_token = Lexer.token
34+
35+
exception ParseError = Parser.Error
36+
37+
exception LexError = Lexer.Error
38+
end)
39+
end
40+
41+
module Bound = struct
42+
include Syntax.Bound
43+
end
44+
45+
module Var = Var
46+
module Type_error = Type_error
47+
48+
module Env = struct
49+
include Infer.Env
50+
51+
let assume_val name ty env = add_val env name (Ty_sch.parse_string ty)
52+
53+
let assume_tclass name ty env = add_tclass env name (Ty_sch.parse_string ty)
54+
55+
let assume_tclass_instance name ty env =
56+
add_tclass_instance env name (Ty_sch.parse_string ty)
57+
end
58+
59+
let infer = Infer.infer

0 commit comments

Comments
 (0)