From 996cd9ebb1d673e8e8700e1acbdf54d22d2c7d44 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Sun, 9 Jun 2024 19:04:49 +0900 Subject: [PATCH] :tada: Type-safe random-variable/value/dist distinction Needs more polishing here and there including a full-implementation of partial evaluation, but the current version works too (in principle). The sample STAPPL programs run without friction! --- bin/dune | 12 +- bin/main.ml | 11 +- lib/compiler.ml | 127 +++--- lib/dist.ml | 12 +- lib/dune | 2 +- lib/evaluator.ml | 105 +++-- lib/graph.ml | 6 +- lib/preprocessor.ml | 120 ++++++ lib/printing.ml | 26 +- lib/typed_tree.ml | 121 ++++-- lib/typing.ml | 801 +++++++++++++++++++---------------- samples/normal_bernoulli.png | Bin 23750 -> 23677 bytes samples/simple_itpp.png | Bin 17629 -> 17613 bytes samples/student.png | Bin 16683 -> 16701 bytes 14 files changed, 851 insertions(+), 492 deletions(-) create mode 100644 lib/preprocessor.ml diff --git a/bin/dune b/bin/dune index a817757..b0a19bf 100644 --- a/bin/dune +++ b/bin/dune @@ -1,7 +1,13 @@ (executable (public_name stappl) (name main) - (libraries core core_unix.command_unix core_unix.filename_unix stappl) - (modes byte exe) + (libraries + core + core_unix.command_unix + core_unix.filename_unix + logs + logs.fmt + stappl) (preprocess - (pps ppx_jane))) + (pps ppx_jane)) + (modes byte exe)) diff --git a/bin/main.ml b/bin/main.ml index 768bd6b..bcf4a25 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -35,8 +35,11 @@ let command : Command.t = (let%map_open.Command filename = anon (maybe_with_default "-" ("filename" %: Filename_unix.arg_type)) and pp_opt = flag "-pp" no_arg ~doc:" Pretty print the program" - and graph_opt = flag "-graph" no_arg ~doc:" Print the compiled graph" in + and graph_opt = flag "-graph" no_arg ~doc:" Print the compiled graph" + and debug_opt = flag "-debug" no_arg ~doc:" Debug mode" in fun () -> + if debug_opt then Logs.set_level (Some Logs.Debug); + if pp_opt then ( printf "Pretty-print: %s\n" filename; print_s [%sexp (get_program filename : Parse_tree.program)]); @@ -49,6 +52,7 @@ let command : Command.t = let graph, query = get_program filename |> Compiler.compile_program in graph_query := Some (graph, query); print_s [%sexp (Printing.of_graph graph : Printing.graph)]); + if pp_opt || graph_opt then printf "\n"; printf "Inference: %s\n" filename; Out_channel.flush stdout; @@ -60,4 +64,7 @@ let command : Command.t = printf "Query result saved at %s\n" (Evaluator.infer ~filename graph query)) -let () = Command_unix.run ~version:"0.1.0" ~build_info:"STAPPL" command +let () = + Logs.set_reporter (Logs_fmt.reporter ()); + Command_unix.run ~version:"0.1.0" ~build_info:"STAPPL" command; + exit (if Logs.err_count () > 0 then 1 else 0) diff --git a/lib/compiler.ml b/lib/compiler.ml index 119cf46..e77b7b2 100644 --- a/lib/compiler.ml +++ b/lib/compiler.ml @@ -16,21 +16,24 @@ exception Not_closed_observation let rec peval : type a. (a, det) texp -> (a, det) texp = fun { ty; exp } -> + (* TODO: consider other cases *) let exp = match exp with - | (Value _ | Var _) as e -> e + | Value _ -> exp + | Var _ -> exp | Bop (op, te1, te2) -> ( match (peval te1, peval te2) with - | { exp = Value v1; _ }, { exp = Value v2; _ } -> Value (op.f v1 v2) + (*| { ty = ty1; exp = Value v1 }, { ty = ty2; exp = Value v2 } ->*) + (* Value (op.op v1 v2)*) | te1, te2 -> Bop (op, te1, te2)) | Uop (op, te) -> ( match peval te with - | { exp = Value v; _ } -> Value (op.f v) + (*| { exp = Value v; _ } -> Value (op.op v)*) | e -> Uop (op, e)) | If (te_pred, te_cons, te_alt) -> ( match peval te_pred with - | { exp = Value true; _ } -> (peval te_cons).exp - | { exp = Value false; _ } -> (peval te_alt).exp + (*| { exp = Value true; _ } -> (peval te_cons).exp*) + (*| { exp = Value false; _ } -> (peval te_alt).exp*) | te_pred -> If (te_pred, peval te_cons, peval te_alt)) | Call (f, args) -> ( match peval_args args with @@ -48,7 +51,13 @@ let rec peval : type a. (a, det) texp -> (a, det) texp = log_pmdf = (fun [] -> f.log_pmdf vargs); }, [] )) + | If_pred (p, de) -> ( + let p = peval_pred p and de = peval de in + match p with (* TODO: *) _ -> If_pred (p, de)) + | If_con de -> If_con (peval de) + | If_alt de -> If_alt (peval de) in + { ty; exp } and peval_args : type a. (a, det) args -> (a, det) args * a vargs option = @@ -57,31 +66,57 @@ and peval_args : type a. (a, det) args -> (a, det) args * a vargs option = | te :: tl -> ( match (peval te, peval_args tl) with | { ty; exp = Value v }, (tl, Some vargs) -> - ({ ty; exp = Value v } :: tl, Some ((ty, v) :: vargs)) + ({ ty; exp = Value v } :: tl, Some ((dty_of_ty ty, v) :: vargs)) | te, (tl, _) -> (te :: tl, None)) +and peval_pred : pred -> pred = function + | Empty -> failwith "[Bug] Empty predicate" + | True -> True + | False -> False + | And (p, de) -> ( + match peval de with + | { exp = Value true; _ } -> peval_pred p + | { exp = Value false; _ } -> False + | de -> And (p, de)) + | And_not (p, de) -> ( + match peval de with + | { exp = Value true; _ } -> False + | { exp = Value false; _ } -> peval_pred p + | de -> And_not (p, de)) + +let ( &&& ) p de = peval_pred (And (p, de)) +let ( &&! ) p de = peval_pred (And_not (p, de)) + let rec score : type a. (a, det) texp -> (a, det) texp = function + (* TODO: consider other cases *) | { ty; exp = If (e_pred, e_con, e_alt) } -> let s_con = score e_con and s_alt = score e_alt in { ty; exp = If (e_pred, s_con, s_alt) } | { exp = Call _; _ } as e -> e | _ -> raise Score_invalid_arguments -type pred = (bool, det) texp - let rec compile : - type a. env:env -> pred:pred -> (a, non_det) texp -> Graph.t * (a, det) texp - = - fun ~env ~pred { ty; exp } -> + type a s. + env:env -> ?pred:pred -> (a, non_det) texp -> Graph.t * (a, det) texp = + fun ~env ?(pred = Empty) { ty; exp } -> match exp with - | Value v -> (Graph.empty, { ty; exp = Value v }) + | Value _ as exp -> (Graph.empty, { ty; exp }) | Var x -> ( let (Ex { ty = tyx; exp }) = Map.find_exn env x in match (tyx, ty) with - | Tyi, Tyi -> (Graph.empty, { ty; exp }) - | Tyr, Tyr -> (Graph.empty, { ty; exp }) - | Tyb, Tyb -> (Graph.empty, { ty; exp }) - | _, _ -> assert false) + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Val) -> (Graph.empty, { ty; exp }) + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Val) -> (Graph.empty, { ty; exp }) + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Val) -> (Graph.empty, { ty; exp }) + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Val) -> (Graph.empty, { ty; exp }) + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Rv) -> (Graph.empty, { ty; exp }) + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Rv) -> (Graph.empty, { ty; exp }) + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Rv) -> (Graph.empty, { ty; exp }) + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Rv) -> (Graph.empty, { ty; exp }) + | Dist_ty Tyu, Dist_ty Tyu -> (Graph.empty, { ty; exp }) + | Dist_ty Tyb, Dist_ty Tyb -> (Graph.empty, { ty; exp }) + | Dist_ty Tyi, Dist_ty Tyi -> (Graph.empty, { ty; exp }) + | Dist_ty Tyr, Dist_ty Tyr -> (Graph.empty, { ty; exp }) + | _, _ -> failwith "[Bug] Type mismatch") | Bop (op, e1, e2) -> let g1, te1 = compile ~env ~pred e1 in let g2, te2 = compile ~env ~pred e2 in @@ -91,27 +126,14 @@ let rec compile : (g, { ty; exp = Uop (op, te) }) | If (e_pred, e_con, e_alt) -> ( let g1, de_pred = compile ~env ~pred e_pred in - let pred_con = - peval - { ty = Tyb; exp = Bop ({ f = ( && ); name = "&&" }, pred, de_pred) } - in - let pred_alt = - peval - { - ty = Tyb; - exp = - Bop - ( { f = ( && ); name = "&&" }, - pred, - { ty = Tyb; exp = Uop ({ f = not; name = "!" }, de_pred) } ); - } - in + let pred_con = pred &&& de_pred in + let pred_alt = pred &&! de_pred in let g2, de_con = compile ~env ~pred:pred_con e_con in let g3, de_alt = compile ~env ~pred:pred_alt e_alt in let g = Graph.(g1 @| g2 @| g3) in - match pred_con.exp with - | Value true -> (g, de_con) - | Value false -> (g, de_alt) + match pred_con with + | True -> (g, { ty; exp = If_con de_con }) + | False -> (g, { ty; exp = If_alt de_alt }) | _ -> (g, { ty; exp = If (de_pred, de_con, de_alt) })) | Let (x, e, body) -> let g1, det_exp1 = compile ~env ~pred e in @@ -126,13 +148,13 @@ let rec compile : let g, de = compile ~env ~pred e in let v = gen_vertex () in let de_fvs = fv de.exp in - let f : some_det = Ex (score de) in + let f = score de in let g' = Graph. { vertices = [ v ]; arcs = List.map (Set.to_list de_fvs) ~f:(fun z -> (z, v)); - pmdf_map = Id.Map.singleton v f; + pmdf_map = Id.Map.singleton v (Ex f : some_dist_texp); obs_map = Id.Map.empty; } in @@ -142,21 +164,20 @@ let rec compile : let g2, de2 = compile ~env ~pred e2 in let v = gen_vertex () in let f1 = score de1 in - let f = - { ty; exp = If (pred, f1, { ty; exp = Call (Dist.one ty, []) }) } - in - let fvs = Id.(fv de1.exp @| fv pred.exp) in - if not (Set.is_empty (fv de2.exp)) then raise Not_closed_observation; + let f = { ty = f1.ty; exp = If_pred (pred, f1) } in + let fvs = Id.(fv de1.exp @| fv_pred pred) in + if not (Set.is_empty (fv de2.exp)) then + failwith "[Bug] Not closed observation"; let g' = Graph. { vertices = [ v ]; arcs = List.map (Set.to_list fvs) ~f:(fun z -> (z, v)); - pmdf_map = Id.Map.singleton v (Ex f : some_det); - obs_map = Id.Map.singleton v (Ex de2 : some_det); + pmdf_map = Id.Map.singleton v (Ex f : some_dist_texp); + obs_map = Id.Map.singleton v (Ex de2 : some_dat_texp); } in - Graph.(g1 @| g2 @| g', de2) + Graph.(g1 @| g2 @| g', { ty = Dat_ty (Tyu, Val); exp = Value () }) and compile_args : type a. env -> pred -> (a, non_det) args -> Graph.t * (a, det) args = @@ -168,7 +189,17 @@ and compile_args : let g', args = compile_args env pred args in Graph.(g @| g', arg :: args) -let compile_program (prog : program) : Graph.t * some_det = - let (Ex e) = Typing.check_program prog in - let g, e = compile ~env:Id.Map.empty ~pred:{ ty = Tyb; exp = Value true } e in - (g, Ex e) +exception Query_not_found + +let compile_program (prog : program) : Graph.t * some_rv_texp = + Logs.debug (fun m -> + m "Inlining program %a" Sexp.pp_hum [%sexp (prog : Parse_tree.program)]); + let exp = Preprocessor.inline prog in + Logs.debug (fun m -> + m "Inlined program %a" Sexp.pp_hum [%sexp (exp : Parse_tree.exp)]); + + let (Ex e) = Typing.check exp in + let g, { ty; exp } = compile ~env:Id.Map.empty e in + match ty with + | Dat_ty (_, Rv) -> (g, Ex { ty; exp }) + | _ -> raise Query_not_found diff --git a/lib/dist.ml b/lib/dist.ml index be83080..81c1bae 100644 --- a/lib/dist.ml +++ b/lib/dist.ml @@ -1,7 +1,17 @@ open! Core open Typed_tree -let one : type a. a ty -> (a, unit) dist = function +type some_dist = Ex : _ dist -> some_dist + +let one : type a. a dty -> (a, unit) dist = function + | Tyu -> + { + ret = Tyu; + name = "one"; + params = []; + sampler = (fun [] -> ()); + log_pmdf = (fun [] _ -> 0.0); + } | Tyi -> { ret = Tyi; diff --git a/lib/dune b/lib/dune index 1e1fb83..bf16f5d 100644 --- a/lib/dune +++ b/lib/dune @@ -1,6 +1,6 @@ (library (name stappl) - (libraries core owl owl-plplot string_dict) + (libraries core owl owl-plplot string_dict logs) (inline_tests) (preprocess (pps ppx_jane))) diff --git a/lib/evaluator.ml b/lib/evaluator.ml index 788fe38..214d4f9 100644 --- a/lib/evaluator.ml +++ b/lib/evaluator.ml @@ -1,6 +1,8 @@ open! Core open Typed_tree +type some_v = Ex : ('a dty * 'a) -> some_v + module Ctx = struct type t = some_v Id.Table.t @@ -9,62 +11,94 @@ module Ctx = struct let find_exn = Hashtbl.find_exn end -let rec eval : type a. Ctx.t -> (a, det) texp -> a = +let rec eval_dat : type a s. Ctx.t -> ((a, s) dat_ty, det) texp -> a = fun ctx { ty; exp } -> match exp with | Value v -> v | Var x -> ( let (Ex (tv, v)) = Ctx.find_exn ctx x in match (ty, tv) with - | Tyi, Tyi -> v - | Tyr, Tyr -> v - | Tyb, Tyb -> v + | Dat_ty (Tyu, _), Tyu -> v + | Dat_ty (Tyb, _), Tyb -> v + | Dat_ty (Tyi, _), Tyi -> v + | Dat_ty (Tyr, _), Tyr -> v | _ -> assert false) - | Bop (op, te1, te2) -> op.f (eval ctx te1) (eval ctx te2) - | Uop (op, te) -> op.f (eval ctx te) + | Bop ({ op; _ }, te1, te2) -> op (eval_dat ctx te1) (eval_dat ctx te2) + | Uop ({ op; _ }, te) -> op (eval_dat ctx te) | If (te_pred, te_cons, te_alt) -> - if eval ctx te_pred then eval ctx te_cons else eval ctx te_alt + if eval_dat ctx te_pred then eval_dat ctx te_cons else eval_dat ctx te_alt + | If_con te -> eval_dat ctx te + | If_alt te -> eval_dat ctx te + +and eval_dist : type a. Ctx.t -> (a dist_ty, det) texp -> a = + fun ctx { ty = Dist_ty dty as ty; exp } -> + match exp with | Call (f, args) -> f.sampler (eval_args ctx args) + | Var x -> ( + let (Ex (tv, v)) = Ctx.find_exn ctx x in + match (dty, tv) with + | Tyu, Tyu -> v + | Tyb, Tyb -> v + | Tyi, Tyi -> v + | Tyr, Tyr -> v + | _ -> assert false) + | If_pred (pred, dist) -> + if eval_pred ctx pred then eval_dist ctx dist + else eval_dist ctx { ty; exp = Call (Dist.one dty, []) } + +and eval_pred (ctx : Ctx.t) : pred -> bool = + (*print_endline "[eval_pred]";*) + function + | Empty | True -> true + | False -> false + | And (p, de) -> eval_dat ctx de && eval_pred ctx p + | And_not (p, de) -> (not (eval_dat ctx de)) && eval_pred ctx p and eval_args : type a. Ctx.t -> (a, det) args -> a vargs = fun ctx -> function | [] -> [] - | te :: tl -> (te.ty, eval ctx te) :: eval_args ctx tl + | ({ ty = Dat_ty (dty, _); _ } as te) :: tl -> + (dty, eval_dat ctx te) :: eval_args ctx tl -let rec eval_pmdf (ctx : Ctx.t) (Ex { ty; exp } : some_det) : - (some_v -> float) * some_v = +let rec eval_pmdf : + type a. Ctx.t -> (a dist_ty, det) texp -> (some_v -> real) * some_v = + fun ctx { ty = Dist_ty dty as ty; exp } -> match exp with - | If (te_pred, te_cons, te_alt) -> - if eval ctx te_pred then eval_pmdf ctx (Ex te_cons) - else eval_pmdf ctx (Ex te_alt) + | If_pred (pred, te) -> + if eval_pred ctx pred then eval_pmdf ctx te + else eval_pmdf ctx { ty; exp = Call (Dist.one dty, []) } | Call (f, args) -> let pmdf (Ex (ty', v) : some_v) = - match (ty, ty') with + match (dty, ty') with + | Tyu, Tyu -> f.log_pmdf (eval_args ctx args) v + | Tyb, Tyb -> f.log_pmdf (eval_args ctx args) v | Tyi, Tyi -> f.log_pmdf (eval_args ctx args) v | Tyr, Tyr -> f.log_pmdf (eval_args ctx args) v - | Tyb, Tyb -> f.log_pmdf (eval_args ctx args) v | _, _ -> assert false in - (pmdf, Ex (ty, eval ctx { ty; exp })) + (pmdf, Ex (dty, eval_dist ctx { ty; exp })) | _ -> (* not reachable *) assert false -let gibbs_sampling ~(num_samples : int) (graph : Graph.t) (query : some_det) : - float array = +(* TODO: Remove existential wrapper *) +let gibbs_sampling ~(num_samples : int) (graph : Graph.t) + (Ex query : some_rv_texp) : float array = (* Initialize the context with the observed values. Float conversion must succeed as observed variables do not contain free variables *) - let default : type a. a ty -> a = function + let default : type a. a dty -> a = function + | Tyu -> () + | Tyb -> false | Tyi -> 0 | Tyr -> 0.0 - | Tyb -> false in let ctx = Id.Table.create () in - Map.iteri graph.obs_map ~f:(fun ~key:name ~data:(Ex { ty; exp }) -> - let value : some_v = Ex (ty, eval ctx { ty; exp }) in + Map.iteri graph.obs_map + ~f:(fun ~key:name ~data:(Ex { ty = Dat_ty (dty, _) as ty; exp }) -> + let value : some_v = Ex (dty, eval_dat ctx { ty; exp }) in Ctx.set ctx ~name ~value); let unobserved = Graph.unobserved_vertices_pmdfs graph in - List.iter unobserved ~f:(fun (name, Ex { ty; _ }) -> - let value : some_v = Ex (ty, default ty) in + List.iter unobserved ~f:(fun (name, Ex { ty = Dist_ty dty; _ }) -> + let value : some_v = Ex (dty, default dty) in Ctx.set ctx ~name ~value); (* Adapted from gibbs_sampling of Owl *) @@ -73,7 +107,7 @@ let gibbs_sampling ~(num_samples : int) (graph : Graph.t) (query : some_det) : let samples = Array.create ~len:num_samples 0. in for i = 0 to num_iter - 1 do (* Gibbs step *) - List.iter unobserved ~f:(fun (name, exp) -> + List.iter unobserved ~f:(fun (name, Ex exp) -> let curr = Ctx.find_exn ctx name in let log_pmdf, cand = eval_pmdf ctx exp in @@ -84,12 +118,12 @@ let gibbs_sampling ~(num_samples : int) (graph : Graph.t) (query : some_det) : (* variables influenced by "name" *) let name_infl = - Map.filteri graph.pmdf_map - ~f:(fun ~key:name' ~data:(Ex { exp; _ }) -> + Map.filteri graph.pmdf_map ~f:(fun ~key:name' ~data:(Ex { exp; _ }) -> Id.(name' = name) || Set.mem (fv exp) name) in let log_alpha = - Map.fold name_infl ~init:log_alpha ~f:(fun ~key:name' ~data:exp acc -> + Map.fold name_infl ~init:log_alpha + ~f:(fun ~key:name' ~data:(Ex exp) acc -> let prob_w_cand = (fst (eval_pmdf ctx exp)) (Ctx.find_exn ctx name') in @@ -106,12 +140,13 @@ let gibbs_sampling ~(num_samples : int) (graph : Graph.t) (query : some_det) : if Float.(uniform > alpha) then Ctx.set ctx ~name ~value:curr); if i >= a && i mod b = 0 then - let (Ex query) = query in let query = - match (query.ty, eval ctx query) with - | Tyi, i -> float_of_int i - | Tyr, r -> r - | Tyb, b -> if b then 1.0 else 0.0 + match (query.ty, eval_dat ctx query) with + (* TODO: Fix query type error *) + | Dat_ty (Tyu, Rv), _ -> 0.0 + | Dat_ty (Tyb, Rv), b -> if b then 1.0 else 0.0 + | Dat_ty (Tyi, Rv), i -> float_of_int i + | Dat_ty (Tyr, Rv), r -> r in samples.((i - a) / b) <- query done; @@ -119,7 +154,7 @@ let gibbs_sampling ~(num_samples : int) (graph : Graph.t) (query : some_det) : samples let infer ?(filename : string = "out") ?(num_samples : int = 100_000) - (graph : Graph.t) (query : some_det) : string = + (graph : Graph.t) (query : some_rv_texp) : string = let samples = gibbs_sampling graph ~num_samples query in let filename = String.chop_suffix_if_exists filename ~suffix:".stp" in @@ -127,7 +162,7 @@ let infer ?(filename : string = "out") ?(num_samples : int = 100_000) let open Owl_plplot in let h = Plot.create plot_path in - Plot.set_title h (Printing.to_string query); + Plot.set_title h (Printing.of_rv query); let mat = Owl.Mat.of_array samples 1 num_samples in Plot.histogram ~h ~bin:50 mat; Plot.output h; diff --git a/lib/graph.ml b/lib/graph.ml index f688409..ad66577 100644 --- a/lib/graph.ml +++ b/lib/graph.ml @@ -3,8 +3,8 @@ open Typed_tree type vertex = Id.t type arc = vertex * vertex -type pmdf_map = some_det Id.Map.t -type obs_map = some_det Id.Map.t +type pmdf_map = some_dist_texp Id.Map.t +type obs_map = some_dat_texp Id.Map.t type t = { vertices : vertex list; @@ -36,7 +36,7 @@ let union g1 g2 = let ( @| ) = union let unobserved_vertices_pmdfs ({ vertices; pmdf_map; obs_map; _ } : t) : - (vertex * some_det) list = + (vertex * some_dist_texp) list = List.filter_map vertices ~f:(fun v -> if Map.mem obs_map v then None else diff --git a/lib/preprocessor.ml b/lib/preprocessor.ml new file mode 100644 index 0000000..580a201 --- /dev/null +++ b/lib/preprocessor.ml @@ -0,0 +1,120 @@ +open! Core +open Parse_tree + +type subst_map = Id.t Id.Map.t + +exception Arity_mismatch of string +exception Unbound_variable of string + +let gen_args = + let cnt = ref 0 in + fun () -> + let arg = "$arg" ^ string_of_int !cnt in + incr cnt; + arg + +let rec subst (env : subst_map) : exp -> exp = + (* 𝜂-expansion required to avoid infinite recursion *) + let subst' e = subst env e in + + function + | (Int _ | Real _ | Bool _) as e -> e + | Var x -> ( + match Map.find env x with + | None -> raise (Unbound_variable x) + | Some v -> Var v) + | Add (e1, e2) -> Add (subst' e1, subst' e2) + | Radd (e1, e2) -> Radd (subst' e1, subst' e2) + | Minus (e1, e2) -> Minus (subst' e1, subst' e2) + | Rminus (e1, e2) -> Rminus (subst' e1, subst' e2) + | Neg e -> Neg (subst' e) + | Rneg e -> Rneg (subst' e) + | Mult (e1, e2) -> Mult (subst' e1, subst' e2) + | Rmult (e1, e2) -> Rmult (subst' e1, subst' e2) + | Div (e1, e2) -> Div (subst' e1, subst' e2) + | Rdiv (e1, e2) -> Rdiv (subst' e1, subst' e2) + | Eq (e1, e2) -> Eq (subst' e1, subst' e2) + | Req (e1, e2) -> Req (subst' e1, subst' e2) + | Noteq (e1, e2) -> Noteq (subst' e1, subst' e2) + | Less (e1, e2) -> Less (subst' e1, subst' e2) + | Rless (e1, e2) -> Rless (subst' e1, subst' e2) + | And (e1, e2) -> And (subst' e1, subst' e2) + | Or (e1, e2) -> Or (subst' e1, subst' e2) + | Seq (e1, e2) -> Seq (subst' e1, subst' e2) + | Not e -> Not (subst' e) + | Assign (x, e1, e2) -> + Assign (x, subst' e1, subst (Map.set env ~key:x ~data:x) e2) + | If (e_pred, e_con, e_alt) -> If (subst' e_pred, subst' e_con, subst' e_alt) + | Call (f, args) -> + let args = List.map ~f:subst' args in + Call (f, args) + | Sample e -> Sample (subst' e) + | Observe (d, e) -> Observe (subst' d, subst' e) + | List _ -> failwith "List not implemented" + | Record _ -> failwith "Record not implemented" + +let rec inline_one (fn : fn) (prog : program) = + let rec inline_exp scope (exp : exp) = + let inline_exp' = inline_exp scope in + match exp with + | (Int _ | Real _ | Bool _) as e -> e + | Var x as e -> if Set.mem scope x then e else raise (Unbound_variable x) + | Add (e1, e2) -> Add (inline_exp' e1, inline_exp' e2) + | Radd (e1, e2) -> Radd (inline_exp' e1, inline_exp' e2) + | Minus (e1, e2) -> Minus (inline_exp' e1, inline_exp' e2) + | Rminus (e1, e2) -> Rminus (inline_exp' e1, inline_exp' e2) + | Neg e -> Neg (inline_exp' e) + | Rneg e -> Rneg (inline_exp' e) + | Mult (e1, e2) -> Mult (inline_exp' e1, inline_exp' e2) + | Rmult (e1, e2) -> Rmult (inline_exp' e1, inline_exp' e2) + | Div (e1, e2) -> Div (inline_exp' e1, inline_exp' e2) + | Rdiv (e1, e2) -> Rdiv (inline_exp' e1, inline_exp' e2) + | Eq (e1, e2) -> Eq (inline_exp' e1, inline_exp' e2) + | Req (e1, e2) -> Req (inline_exp' e1, inline_exp' e2) + | Noteq (e1, e2) -> Noteq (inline_exp' e1, inline_exp' e2) + | Less (e1, e2) -> Less (inline_exp' e1, inline_exp' e2) + | Rless (e1, e2) -> Rless (inline_exp' e1, inline_exp' e2) + | And (e1, e2) -> And (inline_exp' e1, inline_exp' e2) + | Or (e1, e2) -> Or (inline_exp' e1, inline_exp' e2) + | Seq (e1, e2) -> Seq (inline_exp' e1, inline_exp' e2) + | Not e -> Not (inline_exp' e) + | Assign (x, e1, e2) -> + Assign (x, inline_exp' e1, inline_exp (Set.add scope x) e2) + | If (e_pred, e_con, e_alt) -> + If (inline_exp' e_pred, inline_exp' e_con, inline_exp' e_alt) + | Call (f, args) -> + (* A-Normalize the arguments. For example, f(sample(e)) should only evaluate sample(e) once. *) + let args = List.map ~f:inline_exp' args in + if Id.(f <> fn.name) then Call (f, args) + else + let param_args = + try List.zip_exn fn.params args + with _ -> raise (Arity_mismatch fn.name) + in + let param_args = + List.map ~f:(fun (p, a) -> (p, gen_args (), a)) param_args + in + let env = + List.fold param_args ~init:Id.Map.empty ~f:(fun env (p, p', _a) -> + Map.set env ~key:p ~data:p') + in + List.fold param_args ~init:(subst env fn.body) + ~f:(fun body (_p, p', a) -> Assign (p', a, body)) + | Sample e -> Sample (inline_exp' e) + | Observe (d, e) -> Observe (inline_exp' d, inline_exp' e) + | List _ -> failwith "List not implemented" + | Record _ -> failwith "Record not implemented" + in + + let { funs; exp } = prog in + match funs with + | [] -> { funs = []; exp = inline_exp Id.Set.empty exp } + | { name; params; body } :: funs -> + let body = inline_exp (Id.Set.of_list params) body in + if Id.(name = fn.name) then { funs = { name; params; body } :: funs; exp } + else + let { funs; exp } = inline_one fn { funs; exp } in + { funs = { name; params; body } :: funs; exp } + +let rec inline ({ funs; exp } : program) : exp = + match funs with [] -> exp | f :: funs -> inline (inline_one f { funs; exp }) diff --git a/lib/printing.ml b/lib/printing.ml index 793c935..88873b3 100644 --- a/lib/printing.ml +++ b/lib/printing.ml @@ -2,7 +2,7 @@ open! Core open Typed_tree type t = - | Value : Id.t -> t + | Value : string -> t | Var : Id.t -> t | Bop : Id.t * t * t -> t | Uop : Id.t * t -> t @@ -10,6 +10,8 @@ type t = (*| List : ('a, 'd) exp list -> ('a list, 'd) exp*) (*| Record : ('k * 'v, 'd) exp list -> ('k * 'v, 'd) exp*) | If : t * t * t -> t + | If_con : t -> t + | If_alt : t -> t | Let : Id.t * t * t -> t | Call : Id.t * t list -> t | Sample : t -> t @@ -27,15 +29,19 @@ type graph = { let rec of_exp : type a d. (a, d) texp -> t = fun { ty; exp } -> match exp with + | If (pred, cons, alt) -> If (of_exp pred, of_exp cons, of_exp alt) + | If_pred (pred, cons) -> If (of_pred pred, of_exp cons, Value "1") + | If_con exp -> If_con (of_exp exp) + | If_alt exp -> If_alt (of_exp exp) | Value v -> ( match ty with - | Tyi -> Value (string_of_int v) - | Tyr -> Value (string_of_float v) - | Tyb -> Value (string_of_bool v)) + | Dat_ty (Tyu, _) -> Value "()" + | Dat_ty (Tyi, _) -> Value (string_of_int v) + | Dat_ty (Tyr, _) -> Value (string_of_float v) + | Dat_ty (Tyb, _) -> Value (string_of_bool v)) | Var v -> Var v | Bop (op, e1, e2) -> Bop (op.name, of_exp e1, of_exp e2) | Uop (op, e) -> Uop (op.name, of_exp e) - | If (pred, cons, alt) -> If (of_exp pred, of_exp cons, of_exp alt) | Let (x, e1, e2) -> Let (x, of_exp e1, of_exp e2) | Call (f, args) -> Call (f.name, of_args args) | Sample e -> Sample (of_exp e) @@ -45,6 +51,13 @@ and of_args : type a d. (a, d) args -> t list = function | [] -> [] | arg :: args -> of_exp arg :: of_args args +and of_pred : pred -> t = function + | Empty -> Value "" + | True -> Value "true" + | False -> Value "false" + | And (pred, exp) -> Bop ("&&", of_pred pred, of_exp exp) + | And_not (pred, exp) -> Bop ("&&", of_pred pred, Uop ("not", of_exp exp)) + let of_graph ({ vertices; arcs; pmdf_map; obs_map } : Graph.t) : graph = { vertices; @@ -53,4 +66,5 @@ let of_graph ({ vertices; arcs; pmdf_map; obs_map } : Graph.t) : graph = obs_map = Map.map obs_map ~f:(fun (Ex e) -> of_exp e); } -let to_string (Ex e : some_det) = e |> of_exp |> sexp_of_t |> Sexp.to_string_hum +let of_rv (Ex rv : some_rv_texp) = + rv |> of_exp |> sexp_of_t |> Sexp.to_string_hum diff --git a/lib/typed_tree.ml b/lib/typed_tree.ml index 2198738..7cd8f7f 100644 --- a/lib/typed_tree.ml +++ b/lib/typed_tree.ml @@ -1,54 +1,114 @@ open! Core type real = float -type _ ty = Tyi : int ty | Tyr : real ty | Tyb : bool ty +type _ dty = Tyu : unit dty | Tyi : int dty | Tyr : real dty | Tyb : bool dty +type value = Val_ph +type rv = Rv_ph +type _ stamp = Val : value stamp | Rv : rv stamp +type ('a, 'b) dat_ty = Dat_ty_ph +type 'a dist_ty = Dist_ty_ph -let string_of_ty : type a. a ty -> string = function - | Tyi -> "int" - | Tyr -> "real" - | Tyb -> "bool" +type _ ty = + | Dat_ty : 'a dty * 'b stamp -> ('a, 'b) dat_ty ty + | Dist_ty : 'a dty -> 'a dist_ty ty type _ params = | [] : unit params - | ( :: ) : 'a ty * 'b params -> ('a * 'b) params + | ( :: ) : 'a dty * 'b params -> ('a * 'b) params -type det = Det -type non_det = Non_det +type det = Det_ph +type non_det = Non_det_ph type _ vargs = | [] : unit vargs - | ( :: ) : ('a ty * 'a) * 'b vargs -> ('a * 'b) vargs + | ( :: ) : ('a dty * 'a) * 'b vargs -> ('a * 'b) vargs type ('a, 'b) dist = { - ret : 'a ty; + ret : 'a dty; name : Id.t; params : 'b params; sampler : 'b vargs -> 'a; log_pmdf : 'b vargs -> 'a -> real; } -type ('a, 'b, 'c) bop = { name : Id.t; f : 'a -> 'b -> 'c } -type ('a, 'b) uop = { name : Id.t; f : 'a -> 'b } +type ('a, 'b, 'c) bop = { name : Id.t; op : 'a -> 'b -> 'c } +type ('a, 'b) uop = { name : Id.t; op : 'a -> 'b } +(* TODO: Why args should also be det? *) type (_, _) args = | [] : (unit, _) args - | ( :: ) : ('a, 'd) texp * ('b, 'd) args -> ('a * 'b, 'd) args + | ( :: ) : (('a, _) dat_ty, 'd) texp * ('b, 'd) args -> ('a * 'b, 'd) args + +and pred = + | Empty : pred + | True : pred + | False : pred + | And : pred * ((bool, _) dat_ty, det) texp -> pred + | And_not : pred * ((bool, _) dat_ty, det) texp -> pred + +and ('a, 'd) texp = { ty : 'a ty; exp : ('a, 'd) exp } and (_, _) exp = - | Value : 'a -> ('a, _) exp + | Value : 'a -> (('a, value) dat_ty, _) exp | Var : Id.t -> _ exp - | Bop : ('a, 'b, 'c) bop * ('a, 'd) texp * ('b, 'd) texp -> ('c, 'd) exp - | Uop : ('a, 'b) uop * ('a, 'd) texp -> ('b, 'd) exp - (* TODO: Add list and record constructors *) - (*| List : ('a, 'd) exp list -> ('a list, 'd) exp*) - (*| Record : ('k * 'v, 'd) exp list -> ('k * 'v, 'd) exp*) - | If : (bool, 'd) texp * ('a, 'd) texp * ('a, 'd) texp -> ('a, 'd) exp + | Bop : + ('a, 'b, 'c) bop * (('a, _) dat_ty, 'd) texp * (('b, _) dat_ty, 'd) texp + -> (('c, _) dat_ty, 'd) exp + | Uop : ('a, 'b) uop * (('a, _) dat_ty, 'd) texp -> (('b, _) dat_ty, 'd) exp + | If : + ((bool, _) dat_ty, 'd) texp + * (('a, _) dat_ty, 'd) texp + * (('a, _) dat_ty, 'd) texp + -> (('a, _) dat_ty, 'd) exp + | If_pred : pred * ('a dist_ty, det) texp -> ('a dist_ty, det) exp + | If_con : (('a, 's) dat_ty, det) texp -> (('a, _) dat_ty, det) exp + | If_alt : (('a, 's) dat_ty, det) texp -> (('a, _) dat_ty, det) exp | Let : Id.t * ('a, non_det) texp * ('b, non_det) texp -> ('b, non_det) exp - | Call : ('a, 'b) dist * ('b, 'd) args -> ('a, 'd) exp - | Sample : ('a, non_det) texp -> ('a, non_det) exp - | Observe : ('a, non_det) texp * ('a, non_det) texp -> ('a, non_det) exp + | Call : ('a, 'b) dist * ('b, 'd) args -> ('a dist_ty, 'd) exp + | Sample : ('a dist_ty, non_det) texp -> (('a, rv) dat_ty, non_det) exp + | Observe : + ('a dist_ty, non_det) texp * (('a, value) dat_ty, non_det) texp + -> ((unit, value) dat_ty, non_det) exp -and ('a, 'd) texp = { ty : 'a ty; exp : ('a, 'd) exp } +type some_non_det_texp = Ex : (_, non_det) texp -> some_non_det_texp +type some_det = Ex : (_, det) texp -> some_det +type some_rv_texp = Ex : ((_, rv) dat_ty, det) texp -> some_rv_texp +type some_dat_texp = Ex : ((_, value) dat_ty, det) texp -> some_dat_texp +type some_dist_texp = Ex : (_ dist_ty, det) texp -> some_dist_texp +type some_dty = Ex : _ dty -> some_dty +type some_ty = Ex : _ ty -> some_ty +type some_stamp = Ex : _ stamp -> some_stamp + +type _ some_dat_non_det_texp = + | Ex : (('a, _) dat_ty, non_det) texp -> 'a some_dat_non_det_texp + +type 'a dist_non_det_texp = ('a dist_ty, non_det) texp +(* | Ex : ('a dist_ty ty, non_det) texp -> 'a some_dist_non_det_texp*) + +(*type _ some_dist_texp = Ex : ('a dist_ty, non_det) texp -> 'a some_dist_texp*) + +let dty_of_ty : type a. (a, _) dat_ty ty -> a dty = function + | Dat_ty (dty, _) -> dty + +let string_of_dty : type a. a dty -> string = function + | Tyu -> "unit" + | Tyi -> "int" + | Tyr -> "real" + | Tyb -> "bool" + +let string_of_ty : type a. a ty -> string = function + | Dat_ty (Tyu, Val) -> "unit val" + | Dat_ty (Tyi, Val) -> "int val" + | Dat_ty (Tyr, Val) -> "real val" + | Dat_ty (Tyb, Val) -> "bool val" + | Dat_ty (Tyu, Rv) -> "unit rv" + | Dat_ty (Tyi, Rv) -> "int rv" + | Dat_ty (Tyr, Rv) -> "real rv" + | Dat_ty (Tyb, Rv) -> "bool rv" + | Dist_ty Tyu -> "unit dist" + | Dist_ty Tyi -> "int dist" + | Dist_ty Tyr -> "real dist" + | Dist_ty Tyb -> "bool dist" let rec fv : type a. (a, det) exp -> Id.Set.t = function | Value _ -> Id.Set.empty @@ -57,15 +117,16 @@ let rec fv : type a. (a, det) exp -> Id.Set.t = function | Uop (_, { exp; _ }) -> fv exp | If ({ exp = e_pred; _ }, { exp = e_cons; _ }, { exp = e_alt; _ }) -> Id.(fv e_pred @| fv e_cons @| fv e_alt) + | If_pred (pred, { exp = e_cons; _ }) -> Id.(fv_pred pred @| fv e_cons) + | If_con { exp; _ } -> fv exp + | If_alt { exp; _ } -> fv exp | Call (_, args) -> fv_args args and fv_args : type a. (a, det) args -> Id.Set.t = function | [] -> Id.Set.empty | { exp; _ } :: es -> Id.(fv exp @| fv_args es) -type some_ndet = Ex : (_, non_det) texp -> some_ndet -type some_det = Ex : (_, det) texp -> some_det -type some_ty = Ex : _ ty -> some_ty -type some_params = Ex : _ params -> some_params -type some_v = Ex : ('a ty * 'a) -> some_v -type some_dist = Ex : _ dist -> some_dist +and fv_pred : pred -> Id.Set.t = function + | Empty | True | False -> Id.Set.empty + | And (p, { exp = de; _ }) -> Id.(fv de @| fv_pred p) + | And_not (p, { exp = de; _ }) -> Id.(fv de @| fv_pred p) diff --git a/lib/typing.ml b/lib/typing.ml index 6614b6b..590ebc7 100644 --- a/lib/typing.ml +++ b/lib/typing.ml @@ -5,415 +5,490 @@ type tyenv = some_ty Id.Map.t exception Arity_mismatch of string exception Unbound_variable of string -exception Type_mismatch of string +exception Type_error of string -let gen_args = - let cnt = ref 0 in - fun () -> - let arg = "$arg" ^ string_of_int !cnt in - incr cnt; - arg +let raise_if_type_error (exp : Parse_tree.exp) (t1 : _ ty) (t2 : _ ty) : _ = + raise + (Type_error + (sprintf + "Branches of an if statement must return the same type: got (%s) and \ + (%s) in %s" + (string_of_ty t1) (string_of_ty t2) + ([%sexp (exp : Parse_tree.exp)] |> Sexp.to_string_hum))) -let rec subst (env : Id.t Id.Map.t) : Parse_tree.exp -> Parse_tree.exp = - let subst' = subst env in +let get_bop : + type a b c. Parse_tree.exp * (a dty * b dty * c dty) -> (a, b, c) bop = function - | (Int _ | Real _ | Bool _) as e -> e - | Var x -> ( - match Map.find env x with - | None -> raise (Unbound_variable x) - | Some v -> Var v) - | Add (e1, e2) -> Add (subst' e1, subst' e2) - | Radd (e1, e2) -> Radd (subst' e1, subst' e2) - | Minus (e1, e2) -> Minus (subst' e1, subst' e2) - | Rminus (e1, e2) -> Rminus (subst' e1, subst' e2) - | Neg e -> Neg (subst' e) - | Rneg e -> Rneg (subst' e) - | Mult (e1, e2) -> Mult (subst' e1, subst' e2) - | Rmult (e1, e2) -> Rmult (subst' e1, subst' e2) - | Div (e1, e2) -> Div (subst' e1, subst' e2) - | Rdiv (e1, e2) -> Rdiv (subst' e1, subst' e2) - | Eq (e1, e2) -> Eq (subst' e1, subst' e2) - | Req (e1, e2) -> Req (subst' e1, subst' e2) - | Noteq (e1, e2) -> Noteq (subst' e1, subst' e2) - | Less (e1, e2) -> Less (subst' e1, subst' e2) - | Rless (e1, e2) -> Rless (subst' e1, subst' e2) - | And (e1, e2) -> And (subst' e1, subst' e2) - | Or (e1, e2) -> Or (subst' e1, subst' e2) - | Seq (e1, e2) -> Seq (subst' e1, subst' e2) - | Not e -> Not (subst' e) - | Assign (x, e1, e2) -> - Assign (x, subst' e1, subst (Map.set env ~key:x ~data:x) e2) - | If (cond, yes, no) -> If (subst' cond, subst' yes, subst' no) - | Call (f, args) -> - let args = List.map ~f:subst' args in - Call (f, args) - | Sample e -> Sample (subst' e) - | Observe (d, e) -> Observe (subst' d, subst' e) - | List _ -> failwith "List not implemented" - | Record _ -> failwith "Record not implemented" - -let rec inline_one (fn : Parse_tree.fn) (prog : Parse_tree.program) = - let open Parse_tree in - let rec inline_exp scope (e : exp) = - let inline_exp' = inline_exp scope in - match e with - | Int _ | Real _ | Bool _ -> e - | Var x -> if Set.mem scope x then e else raise (Unbound_variable x) - | Add (e1, e2) -> Add (inline_exp' e1, inline_exp' e2) - | Radd (e1, e2) -> Radd (inline_exp' e1, inline_exp' e2) - | Minus (e1, e2) -> Minus (inline_exp' e1, inline_exp' e2) - | Rminus (e1, e2) -> Rminus (inline_exp' e1, inline_exp' e2) - | Neg e -> Neg (inline_exp' e) - | Rneg e -> Rneg (inline_exp' e) - | Mult (e1, e2) -> Mult (inline_exp' e1, inline_exp' e2) - | Rmult (e1, e2) -> Rmult (inline_exp' e1, inline_exp' e2) - | Div (e1, e2) -> Div (inline_exp' e1, inline_exp' e2) - | Rdiv (e1, e2) -> Rdiv (inline_exp' e1, inline_exp' e2) - | Eq (e1, e2) -> Eq (inline_exp' e1, inline_exp' e2) - | Req (e1, e2) -> Req (inline_exp' e1, inline_exp' e2) - | Noteq (e1, e2) -> Noteq (inline_exp' e1, inline_exp' e2) - | Less (e1, e2) -> Less (inline_exp' e1, inline_exp' e2) - | Rless (e1, e2) -> Rless (inline_exp' e1, inline_exp' e2) - | And (e1, e2) -> And (inline_exp' e1, inline_exp' e2) - | Or (e1, e2) -> Or (inline_exp' e1, inline_exp' e2) - | Seq (e1, e2) -> Seq (inline_exp' e1, inline_exp' e2) - | Not e -> Not (inline_exp' e) - | Assign (x, e1, e2) -> - Assign (x, inline_exp' e1, inline_exp (Set.add scope x) e2) - | If (cond, yes, no) -> - If (inline_exp' cond, inline_exp' yes, inline_exp' no) - | Call (f, args) as e -> - (* A-Normalize the arguments. For example, f(sample(e)) should only evaluate sample(e) once. *) - let args = List.map ~f:inline_exp' args in - if Id.(f <> fn.name) then e - else - let param_args = - try List.zip_exn fn.params args - with _ -> raise (Arity_mismatch fn.name) - in - let param_args = - List.map ~f:(fun (p, a) -> (p, gen_args (), a)) param_args - in - let env = - List.fold param_args ~init:Id.Map.empty ~f:(fun env (p, p', _a) -> - Map.set env ~key:p ~data:p') - in - List.fold param_args ~init:(subst env fn.body) - ~f:(fun body (_p, p', a) -> Assign (p', a, body)) - | Sample e -> Sample (inline_exp' e) - | Observe (d, e) -> Observe (inline_exp' d, inline_exp' e) - | List _ -> failwith "List not implemented" - | Record _ -> failwith "Record not implemented" - in + | Add _, (Tyi, Tyi, Tyi) -> { name = "+"; op = ( + ) } + | Radd _, (Tyr, Tyr, Tyr) -> { name = "+."; op = ( +. ) } + | Minus _, (Tyi, Tyi, Tyi) -> { name = "-"; op = ( - ) } + | Rminus _, (Tyr, Tyr, Tyr) -> { name = "-."; op = ( -. ) } + | Mult _, (Tyi, Tyi, Tyi) -> { name = "*"; op = ( * ) } + | Rmult _, (Tyr, Tyr, Tyr) -> { name = "*."; op = ( *. ) } + | Div _, (Tyi, Tyi, Tyi) -> { name = "/"; op = ( / ) } + | Rdiv _, (Tyr, Tyr, Tyr) -> { name = "/."; op = ( /. ) } + | Eq _, (Tyi, Tyi, Tyb) -> { name = "="; op = ( = ) } + | Req _, (Tyr, Tyr, Tyb) -> { name = "=."; op = Float.( = ) } + | Noteq _, (Tyi, Tyi, Tyb) -> { name = "<>"; op = ( <> ) } + | Less _, (Tyi, Tyi, Tyb) -> { name = "<"; op = ( < ) } + | Rless _, (Tyr, Tyr, Tyb) -> { name = "<."; op = Float.( < ) } + | And _, (Tyb, Tyb, Tyb) -> { name = "&&"; op = ( && ) } + | Or _, (Tyb, Tyb, Tyb) -> { name = "||"; op = ( || ) } + | _ -> raise (Type_error "Expected binary operation") - let { funs; exp } = prog in - match funs with - | [] -> { funs = []; exp = inline_exp Id.Set.empty exp } - | { name; params; body } :: funs -> - let body = inline_exp (Id.Set.of_list params) body in - if Id.(name = fn.name) then { funs = { name; params; body } :: funs; exp } - else - let { funs; exp } = inline_one fn { funs; exp } in - { funs = { name; params; body } :: funs; exp } +let get_uop : type a b. Parse_tree.exp * (a dty * b dty) -> (a, b) uop = + function + | Neg _, (Tyi, Tyi) -> { name = "~-"; op = ( ~- ) } + | Rneg _, (Tyr, Tyr) -> { name = "~-."; op = ( ~-. ) } + | Not _, (Tyb, Tyb) -> { name = "!"; op = not } + | e, _ -> + raise + (Type_error + ("Expected unary operation, got " + ^ ([%sexp (e : Parse_tree.exp)] |> Sexp.to_string_hum))) -let rec inline (prog : Parse_tree.program) = - let open Parse_tree in - let { funs; exp } = prog in - match funs with - | [] -> exp - | fn :: funs -> inline (inline_one fn { funs; exp }) +let rec check_dat : + type a. tyenv -> Parse_tree.exp * a dty -> a some_dat_non_det_texp = + fun tyenv (exp, dty) -> + Logs.debug (fun m -> + m "Checking exp (%a : %a)" Sexp.pp_hum + [%sexp (exp : Parse_tree.exp)] + (fun fmt dty -> Format.pp_print_string fmt (string_of_dty dty)) + dty); -let rec check : type a. tyenv -> Parse_tree.exp -> a ty -> (a, non_det) texp = - fun tyenv e ty -> - match e with + match exp with | Var x -> ( match Map.find tyenv x with | None -> raise (Unbound_variable x) - | Some (Ex t) -> ( - match (t, ty) with - | Tyi, Tyi -> { ty; exp = Var x } - | Tyr, Tyr -> { ty; exp = Var x } - | Tyb, Tyb -> { ty; exp = Var x } - | t1, t2 -> + | Some (Ex ty_x) -> ( + match (ty_x, dty) with + | Dat_ty (Tyu, _), Tyu -> Ex { ty = ty_x; exp = Var x } + | Dat_ty (Tyi, _), Tyi -> Ex { ty = ty_x; exp = Var x } + | Dat_ty (Tyr, _), Tyr -> Ex { ty = ty_x; exp = Var x } + | Dat_ty (Tyb, _), Tyb -> Ex { ty = ty_x; exp = Var x } + | ty_x, dty -> raise - (Type_mismatch - (sprintf "Variable %s: expected %s, got %s" x - (string_of_ty t2) (string_of_ty t1))))) + (Type_error + (sprintf "Variable %s: expected (%s _), got %s" x + (string_of_dty dty) (string_of_ty ty_x))))) | Int i -> ( - match ty with - | Tyi -> { ty; exp = Value i } - | t -> - raise - (Type_mismatch (sprintf "Expected int, got %s" (string_of_ty t)))) - | Add (e1, e2) -> ( - match ty with - | Tyi -> check_bop tyenv "+" ( + ) Tyi Tyi Tyi e1 e2 - | t -> - raise - (Type_mismatch - (sprintf "Expected int for Add, got %s" (string_of_ty t)))) - | Minus (e1, e2) -> ( - match ty with - | Tyi -> check_bop tyenv "-" ( - ) Tyi Tyi Tyi e1 e2 - | t -> - raise - (Type_mismatch - (sprintf "Expected int for Minus, got %s" (string_of_ty t)))) - | Neg e -> ( - match ty with - | Tyi -> check_uop tyenv "-" Int.neg Tyi Tyi e - | t -> + match dty with + | Tyi -> Ex { ty = Dat_ty (Tyi, Val); exp = Value i } + | dty -> raise - (Type_mismatch - (sprintf "Expected int for Neg, got %s" (string_of_ty t)))) - | Mult (e1, e2) -> ( - match ty with - | Tyi -> check_bop tyenv "*" ( * ) Tyi Tyi Tyi e1 e2 - | t -> - raise - (Type_mismatch - (sprintf "Expected int for Mult, got %s" (string_of_ty t)))) - | Div (e1, e2) -> ( - match ty with - | Tyi -> check_bop tyenv "/" ( / ) Tyi Tyi Tyi e1 e2 - | t -> + (Type_error (sprintf "Expected int, got %s" (string_of_dty dty)))) + | Bool b -> ( + match dty with + | Tyb -> Ex { ty = Dat_ty (Tyb, Val); exp = Value b } + | dty -> raise - (Type_mismatch - (sprintf "Expected int for Div, got %s" (string_of_ty t)))) + (Type_error (sprintf "Expected bool, got %s" (string_of_dty dty)))) | Real r -> ( - match ty with - | Tyr -> { ty; exp = Value r } - | t -> - raise - (Type_mismatch (sprintf "Expected float, got %s" (string_of_ty t)))) - | Radd (e1, e2) -> ( - match ty with - | Tyr -> check_bop tyenv "+" ( +. ) Tyr Tyr Tyr e1 e2 - | t -> - raise - (Type_mismatch - (sprintf "Expected float for Radd, got %s" (string_of_ty t)))) - | Rminus (e1, e2) -> ( - match ty with - | Tyr -> check_bop tyenv "-" ( -. ) Tyr Tyr Tyr e1 e2 - | t -> - raise - (Type_mismatch - (sprintf "Expected float for Rminus, got %s" (string_of_ty t)))) - | Rneg e -> ( - match ty with - | Tyr -> check_uop tyenv "-" Float.neg Tyr Tyr e - | t -> - raise - (Type_mismatch - (sprintf "Expected float for Rneg, got %s" (string_of_ty t)))) - | Rmult (e1, e2) -> ( - match ty with - | Tyr -> check_bop tyenv "*" ( *. ) Tyr Tyr Tyr e1 e2 - | t -> - raise - (Type_mismatch - (sprintf "Expected float for Rmult, got %s" (string_of_ty t)))) - | Rdiv (e1, e2) -> ( - match ty with - | Tyr -> check_bop tyenv "/" ( /. ) Tyr Tyr Tyr e1 e2 - | t -> - raise - (Type_mismatch - (sprintf "Expected float for Rdiv, got %s" (string_of_ty t)))) - | Bool b -> ( - match ty with - | Tyb -> { ty; exp = Value b } - | t -> + match dty with + | Tyr -> Ex { ty = Dat_ty (Tyr, Val); exp = Value r } + | dty -> raise - (Type_mismatch (sprintf "Expected bool, got %s" (string_of_ty t)))) - | Eq (e1, e2) -> ( - match ty with - | Tyb -> check_bop tyenv "=" Int.( = ) Tyi Tyi Tyb e1 e2 - | t -> + (Type_error (sprintf "Expected real, got %s" (string_of_dty dty)))) + | Add (e1, e2) | Minus (e1, e2) | Mult (e1, e2) | Div (e1, e2) -> ( + match dty with + | Tyi -> + let bop = get_bop (exp, (Tyi, Tyi, Tyi)) in + check_bop tyenv bop (e1, Tyi) (e2, Tyi) Tyi + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for Eq, got %s" (string_of_ty t)))) - | Req (e1, e2) -> ( - match ty with - | Tyb -> check_bop tyenv "=" Float.( = ) Tyr Tyr Tyb e1 e2 - | t -> + (Type_error (sprintf "Expected int, got %s" (string_of_dty dty)))) + | Radd (e1, e2) | Rminus (e1, e2) | Rmult (e1, e2) | Rdiv (e1, e2) -> ( + match dty with + | Tyr -> + let bop = get_bop (exp, (Tyr, Tyr, Tyr)) in + check_bop tyenv bop (e1, Tyr) (e2, Tyr) Tyr + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for Req, got %s" (string_of_ty t)))) - | Noteq (e1, e2) -> ( - match ty with - | Tyb -> check_bop tyenv "<>" Int.( <> ) Tyi Tyi Tyb e1 e2 - | t -> + (Type_error (sprintf "Expected real, got %s" (string_of_dty dty)))) + | Eq (e1, e2) | Noteq (e1, e2) | Less (e1, e2) -> ( + match dty with + | Tyb -> + let bop = get_bop (exp, (Tyi, Tyi, Tyb)) in + check_bop tyenv bop (e1, Tyi) (e2, Tyi) Tyb + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for Noteq, got %s" (string_of_ty t)))) - | Less (e1, e2) -> ( - match ty with - | Tyb -> check_bop tyenv "<" Int.( < ) Tyi Tyi Tyb e1 e2 - | t -> + (Type_error (sprintf "Expected int, got %s" (string_of_dty dty)))) + | Req (e1, e2) | Rless (e1, e2) -> ( + match dty with + | Tyb -> + let bop = get_bop (exp, (Tyr, Tyr, Tyb)) in + check_bop tyenv bop (e1, Tyr) (e2, Tyr) Tyb + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for Less, got %s" (string_of_ty t)))) - | Rless (e1, e2) -> ( - match ty with - | Tyb -> check_bop tyenv "<" Float.( < ) Tyr Tyr Tyb e1 e2 - | t -> + (Type_error (sprintf "Expected real, got %s" (string_of_dty dty)))) + | And (e1, e2) | Or (e1, e2) -> ( + match dty with + | Tyb -> + let bop = get_bop (exp, (Tyb, Tyb, Tyb)) in + check_bop tyenv bop (e1, Tyb) (e2, Tyb) Tyb + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for Rless, got %s" (string_of_ty t)))) - | And (e1, e2) -> ( - match ty with - | Tyb -> check_bop tyenv "&&" ( && ) Tyb Tyb Tyb e1 e2 - | t -> + (Type_error (sprintf "Expected bool, got %s" (string_of_dty dty)))) + | Neg e -> ( + match dty with + | Tyi -> + let uop = get_uop (exp, (Tyi, Tyi)) in + check_uop tyenv uop (e, Tyi) Tyi + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for And, got %s" (string_of_ty t)))) - | Or (e1, e2) -> ( - match ty with - | Tyb -> check_bop tyenv "||" ( || ) Tyb Tyb Tyb e1 e2 - | t -> + (Type_error (sprintf "Expected int, got %s" (string_of_dty dty)))) + | Rneg e -> ( + match dty with + | Tyr -> + let uop = get_uop (exp, (Tyr, Tyr)) in + check_uop tyenv uop (e, Tyr) Tyr + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for Or, got %s" (string_of_ty t)))) + (Type_error (sprintf "Expected real, got %s" (string_of_dty dty)))) | Not e -> ( - match ty with - | Tyb -> check_uop tyenv "!" not Tyb Tyb e - | t -> + match dty with + | Tyb -> + let uop = get_uop (exp, (Tyb, Tyb)) in + check_uop tyenv uop (e, Tyb) Tyb + | dty -> raise - (Type_mismatch - (sprintf "Expected bool for Not, got %s" (string_of_ty t)))) - | Observe (d, e) -> ( - let (Ex td) = convert tyenv d in - let (Ex te) = convert tyenv e in - match (ty, td.ty, te.ty) with - | Tyi, Tyi, Tyi -> { ty; exp = Observe (td, te) } - | Tyr, Tyr, Tyr -> { ty; exp = Observe (td, te) } - | Tyb, Tyb, Tyb -> { ty; exp = Observe (td, te) } - | _ -> + (Type_error (sprintf "Expected bool, got %s" (string_of_dty dty)))) + | Observe (de, ve) -> ( + match dty with + | Tyu -> ( + let tde = infer tyenv de in + let tve = infer tyenv ve in + match (tde, tve) with + | ( Ex ({ ty = Dist_ty Tyu; _ } as tde), + Ex ({ ty = Dat_ty (Tyu, Val); _ } as tve) ) -> + Ex { ty = Dat_ty (Tyu, Val); exp = Observe (tde, tve) } + | ( Ex ({ ty = Dist_ty Tyb; _ } as tde), + Ex ({ ty = Dat_ty (Tyb, Val); _ } as tve) ) -> + Ex { ty = Dat_ty (Tyu, Val); exp = Observe (tde, tve) } + | ( Ex ({ ty = Dist_ty Tyi; _ } as tde), + Ex ({ ty = Dat_ty (Tyi, Val); _ } as tve) ) -> + Ex { ty = Dat_ty (Tyu, Val); exp = Observe (tde, tve) } + | ( Ex ({ ty = Dist_ty Tyr; _ } as tde), + Ex ({ ty = Dat_ty (Tyr, Val); _ } as tve) ) -> + Ex { ty = Dat_ty (Tyu, Val); exp = Observe (tde, tve) } + | _, _ -> + (* TODO: more precise error message *) + raise + (Type_error + (sprintf "Arguments to observe have unexpected types"))) + | dty -> raise - (Type_mismatch (sprintf "Argument to observe has different types"))) + (Type_error (sprintf "Expected unit, got %s" (string_of_dty dty)))) | Seq (e1, e2) -> - let (Ex te1) = convert tyenv e1 in - let te2 = check tyenv e2 ty in - { ty; exp = Let ("_", te1, te2) } + let (Ex te1) = infer tyenv e1 in + let (Ex te2) = check_dat tyenv (e2, dty) in + Ex { te2 with exp = Let ("_", te1, te2) } | Assign (x, e1, e2) -> - let (Ex ({ ty = ty1; exp = _ } as te1)) = convert tyenv e1 in - let tyenv = Map.set tyenv ~key:x ~data:(Ex ty1) in - let te2 = check tyenv e2 ty in - { ty; exp = Let (x, te1, te2) } - | If (pred, conseq, alt) -> - let tpred = check tyenv pred Tyb in - let tconseq = check tyenv conseq ty in - let talt = check tyenv alt ty in - { ty; exp = If (tpred, tconseq, talt) } - | Call (prim, args) -> ( - let (Ex dist) = Dist.get_dist prim in - let args = check_args tyenv args dist.params in - match (dist.ret, ty) with - | Tyi, Tyi -> { ty; exp = Call (dist, args) } - | Tyr, Tyr -> { ty; exp = Call (dist, args) } - | Tyb, Tyb -> { ty; exp = Call (dist, args) } - | _ -> - raise - (Type_mismatch - (sprintf "Expected %s for Call, got %s" (string_of_ty dist.ret) - (string_of_ty ty)))) - | Sample e -> - let te = check tyenv e ty in - { ty; exp = Sample te } - | List _ -> raise (Type_mismatch "List not implemented") - | Record _ -> raise (Type_mismatch "Record not implemented") + let (Ex te1) = infer tyenv e1 in + let tyenv = Map.set tyenv ~key:x ~data:(Ex te1.ty) in + let (Ex te2) = check_dat tyenv (e2, dty) in + Ex { te2 with exp = Let (x, te1, te2) } + | If (e_pred, e_con, e_alt) -> ( + let (Ex te_pred) = check_dat tyenv (e_pred, Tyb) in + let (Ex te_con) = check_dat tyenv (e_con, dty) in + let (Ex te_alt) = check_dat tyenv (e_alt, dty) in + match te_pred.ty with + | Dat_ty (Tyb, Val) -> ( + match (te_con.ty, te_alt.ty) with + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) }) + | Dat_ty (Tyb, Rv) -> ( + match (te_con.ty, te_alt.ty) with + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) })) + | Sample e -> ( + let te = check_dist tyenv (e, dty) in + match te.ty with + | Dist_ty Tyu -> Ex { ty = Dat_ty (Tyu, Rv); exp = Sample te } + | Dist_ty Tyb -> Ex { ty = Dat_ty (Tyb, Rv); exp = Sample te } + | Dist_ty Tyi -> Ex { ty = Dat_ty (Tyi, Rv); exp = Sample te } + | Dist_ty Tyr -> Ex { ty = Dat_ty (Tyr, Rv); exp = Sample te }) + | List _ -> raise (Type_error "List not implemented") + | Record _ -> raise (Type_error "Record not implemented") + | Call (f, e) -> + raise + (Type_error + ("Expected data type, got distribution: " ^ f ^ " " + ^ ([%sexp (e : Parse_tree.exp list)] |> Sexp.to_string_hum))) and check_uop : - type arg ret. + type a ret. tyenv -> - Id.t -> - (arg -> ret) -> - arg ty -> - ret ty -> - Parse_tree.exp -> - (ret, non_det) texp = - fun tyenv name f t ty e -> - let te = check tyenv e t in - { ty; exp = Uop ({ name; f }, te) } + (a, ret) uop -> + Parse_tree.exp * a dty -> + ret dty -> + ret some_dat_non_det_texp = + fun tyenv uop (e, t) tret -> + let (Ex ({ ty = Dat_ty (_, s); _ } as te)) = check_dat tyenv (e, t) in + match s with + | Val -> Ex { ty = Dat_ty (tret, Val); exp = Uop (uop, te) } + | _ -> Ex { ty = Dat_ty (tret, Rv); exp = Uop (uop, te) } and check_bop : - type arg1 arg2 ret. + type a1 a2 ret. tyenv -> - Id.t -> - (arg1 -> arg2 -> ret) -> - arg1 ty -> - arg2 ty -> - ret ty -> - Parse_tree.exp -> - Parse_tree.exp -> - (ret, non_det) texp = - fun tyenv name f t1 t2 ty e1 e2 -> - let te1 = check tyenv e1 t1 in - let te2 = check tyenv e2 t2 in - { ty; exp = Bop ({ name; f }, te1, te2) } + (a1, a2, ret) bop -> + Parse_tree.exp * a1 dty -> + Parse_tree.exp * a2 dty -> + ret dty -> + ret some_dat_non_det_texp = + fun tyenv bop (e1, t1) (e2, t2) tret -> + let (Ex ({ ty = Dat_ty (_, s1); _ } as te1)) = check_dat tyenv (e1, t1) in + let (Ex ({ ty = Dat_ty (_, s2); _ } as te2)) = check_dat tyenv (e2, t2) in + match (s1, s2) with + | Val, Val -> Ex { ty = Dat_ty (tret, Val); exp = Bop (bop, te1, te2) } + | _, _ -> Ex { ty = Dat_ty (tret, Rv); exp = Bop (bop, te1, te2) } and check_args : - type a. tyenv -> Parse_tree.exp list -> a params -> (a, non_det) args = - fun tyenv el tyl -> - match tyl with + type a. tyenv -> Id.t -> Parse_tree.exp list * a params -> (a, non_det) args + = + fun tyenv prim (es, dtys) -> + match dtys with | [] -> [] - | argty :: argtys -> ( - match el with - | [] -> failwith "Primitive call failed" + | dty :: dtys -> ( + match es with + | [] -> raise (Arity_mismatch prim) | arg :: args -> - let arg = check tyenv arg argty in - let args = check_args tyenv args argtys in + let (Ex arg) = check_dat tyenv (arg, dty) in + let args = check_args tyenv prim (args, dtys) in arg :: args) -and convert (tyenv : tyenv) (e : Parse_tree.exp) : some_ndet = - match e with +and check_dist : type a. tyenv -> Parse_tree.exp * a dty -> a dist_non_det_texp + = + fun tyenv (exp, dty) -> + Logs.debug (fun m -> + m "Checking exp (%a : %a dist)" Sexp.pp_hum + [%sexp (exp : Parse_tree.exp)] + (fun fmt dty -> Format.pp_print_string fmt (string_of_dty dty)) + dty); + + match exp with | Var x -> ( match Map.find tyenv x with - | None -> failwith ("Unbound variable " ^ x) + | None -> raise (Unbound_variable x) + | Some (Ex ty_x) -> ( + match (ty_x, dty) with + | Dist_ty Tyu, Tyu -> { ty = ty_x; exp = Var x } + | Dist_ty Tyb, Tyb -> { ty = ty_x; exp = Var x } + | Dist_ty Tyi, Tyi -> { ty = ty_x; exp = Var x } + | Dist_ty Tyr, Tyr -> { ty = ty_x; exp = Var x } + | ty_x, dty -> + raise + (Type_error + (sprintf "Variable %s: expected (%s _), got %s" x + (string_of_dty dty) (string_of_ty ty_x))))) + | Seq (e1, e2) -> + let (Ex te1) = infer tyenv e1 in + let te2 = check_dist tyenv (e2, dty) in + { ty = te2.ty; exp = Let ("_", te1, te2) } + | Assign (x, e1, e2) -> + let (Ex te1) = infer tyenv e1 in + let tyenv = Map.set tyenv ~key:x ~data:(Ex te1.ty) in + let te2 = check_dist tyenv (e2, dty) in + { ty = te2.ty; exp = Let (x, te1, te2) } + | If _ -> + raise (Type_error "You cannot return a distribution from a conditional") + | Call (prim, args) -> ( + let (Ex dist) = Dist.get_dist prim in + let args = check_args tyenv dist.name (args, dist.params) in + match (dist.ret, dty) with + | Tyu, Tyu -> { ty = Dist_ty Tyu; exp = Call (dist, args) } + | Tyb, Tyb -> { ty = Dist_ty Tyb; exp = Call (dist, args) } + | Tyi, Tyi -> { ty = Dist_ty Tyi; exp = Call (dist, args) } + | Tyr, Tyr -> { ty = Dist_ty Tyr; exp = Call (dist, args) } + | _ -> + raise + (Type_error + (sprintf "Expected %s for Call, got %s" (string_of_dty dist.ret) + (string_of_dty dty)))) + | Bool _ | Int _ | Real _ | Add _ | Radd _ | Minus _ | Rminus _ | Mult _ + | Rmult _ | Div _ | Rdiv _ | Eq _ | Req _ | Noteq _ | Less _ | Rless _ | And _ + | Or _ | Neg _ | Rneg _ | Not _ | Sample _ | Observe _ | List _ | Record _ -> + raise (Type_error "Expected distribution") + +and infer (tyenv : tyenv) (exp : Parse_tree.exp) : some_non_det_texp = + Logs.debug (fun m -> + m "Infering exp %a" Sexp.pp_hum [%sexp (exp : Parse_tree.exp)]); + match exp with + | Var x -> ( + match Map.find tyenv x with + | None -> raise (Unbound_variable x) | Some (Ex t) -> Ex { ty = t; exp = Var x }) - | Int _ | Add _ | Minus _ | Neg _ | Mult _ | Div _ -> Ex (check tyenv e Tyi) - | Real _ | Radd _ | Rminus _ | Rneg _ | Rmult _ | Rdiv _ -> - Ex (check tyenv e Tyr) - | Bool _ | Eq _ | Req _ | Noteq _ | Less _ | Rless _ | And _ | Or _ | Not _ -> - Ex (check tyenv e Tyb) - | Observe (d, e) -> ( - let (Ex td) = convert tyenv d in - let (Ex te) = convert tyenv e in - match (td.ty, te.ty) with - | Tyi, Tyi -> Ex { ty = Tyi; exp = Observe (td, te) } - | Tyr, Tyr -> Ex { ty = Tyr; exp = Observe (td, te) } - | Tyb, Tyb -> Ex { ty = Tyb; exp = Observe (td, te) } - | _, _ -> failwith "Argument to observe has different types.") + | (Int _ | Add _ | Minus _ | Neg _ | Mult _ | Div _) as e -> + let (Ex t) = check_dat tyenv (e, Tyi) in + Ex t + | (Real _ | Radd _ | Rminus _ | Rneg _ | Rmult _ | Rdiv _) as e -> + let (Ex t) = check_dat tyenv (e, Tyr) in + Ex t + | (Bool _ | Eq _ | Req _ | Noteq _ | Less _ | Rless _ | And _ | Or _ | Not _) + as e -> + let (Ex t) = check_dat tyenv (e, Tyb) in + Ex t + | Observe _ as e -> + let (Ex t) = check_dat tyenv (e, Tyu) in + Ex t | Seq (e1, e2) -> - let (Ex te1) = convert tyenv e1 in - let (Ex ({ ty = ty2; exp = _ } as te2)) = convert tyenv e2 in - Ex { ty = ty2; exp = Let ("_", te1, te2) } + let (Ex te1) = infer tyenv e1 in + let (Ex te2) = infer tyenv e2 in + Ex { ty = te2.ty; exp = Let ("_", te1, te2) } | Assign (x, e1, e2) -> - let (Ex ({ ty = ty1; exp = _ } as te1)) = convert tyenv e1 in - let tyenv = Map.set tyenv ~key:x ~data:(Ex ty1) in - let (Ex ({ ty = ty2; exp = _ } as te2)) = convert tyenv e2 in - Ex { ty = ty2; exp = Let (x, te1, te2) } - | If (pred, conseq, alt) -> ( - let tpred = check tyenv pred Tyb in - let (Ex tconseq) = convert tyenv conseq in - let (Ex talt) = convert tyenv alt in - match (tconseq.ty, talt.ty) with - | Tyi, Tyi -> Ex { ty = Tyi; exp = If (tpred, tconseq, talt) } - | Tyr, Tyr -> Ex { ty = Tyr; exp = If (tpred, tconseq, talt) } - | Tyb, Tyb -> Ex { ty = Tyb; exp = If (tpred, tconseq, talt) } - | _, _ -> failwith "Branches of an if statement must return the same type" - ) + let (Ex te1) = infer tyenv e1 in + let tyenv = Map.set tyenv ~key:x ~data:(Ex te1.ty) in + let (Ex te2) = infer tyenv e2 in + Ex { ty = te2.ty; exp = Let (x, te1, te2) } + | If (e_pred, e_con, e_alt) -> ( + let (Ex te_pred) = check_dat tyenv (e_pred, Tyb) in + let (Ex te_con) = infer tyenv e_con in + let (Ex te_alt) = infer tyenv e_alt in + match te_pred.ty with + | Dat_ty (Tyb, Val) -> ( + match (te_con.ty, te_alt.ty) with + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Val); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | t1, t2 -> raise_if_type_error exp t1 t2) + | Dat_ty (Tyb, Rv) -> ( + match (te_con.ty, te_alt.ty) with + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Val), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Val) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyu, Rv), Dat_ty (Tyu, Rv) -> + Ex { ty = Dat_ty (Tyu, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Val), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Val) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyb, Rv), Dat_ty (Tyb, Rv) -> + Ex { ty = Dat_ty (Tyb, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Val), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Val) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyi, Rv), Dat_ty (Tyi, Rv) -> + Ex { ty = Dat_ty (Tyi, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Val), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Val) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | Dat_ty (Tyr, Rv), Dat_ty (Tyr, Rv) -> + Ex { ty = Dat_ty (Tyr, Rv); exp = If (te_pred, te_con, te_alt) } + | t1, t2 -> raise_if_type_error exp t1 t2)) | Call (prim, args) -> let (Ex dist) = Dist.get_dist prim in - let args = check_args tyenv args dist.params in - Ex { ty = dist.ret; exp = Call (dist, args) } - | Sample e -> - let (Ex te) = convert tyenv e in - Ex { ty = te.ty; exp = Sample te } + let args = check_args tyenv prim (args, dist.params) in + Ex { ty = Dist_ty dist.ret; exp = Call (dist, args) } + | Sample e -> ( + let (Ex te) = infer tyenv e in + match te with + | { ty = Dist_ty Tyu; _ } -> Ex { ty = Dat_ty (Tyu, Rv); exp = Sample te } + | { ty = Dist_ty Tyb; _ } -> Ex { ty = Dat_ty (Tyb, Rv); exp = Sample te } + | { ty = Dist_ty Tyi; _ } -> Ex { ty = Dat_ty (Tyi, Rv); exp = Sample te } + | { ty = Dist_ty Tyr; _ } -> Ex { ty = Dat_ty (Tyr, Rv); exp = Sample te } + | _ -> raise (Type_error "Expected distribution")) | List _ -> failwith "List not implemented" | Record _ -> failwith "Record not implemented" -let check_program (program : Parse_tree.program) : some_ndet = - convert Id.Map.empty (inline program) +let check : Parse_tree.exp -> some_non_det_texp = infer Id.Map.empty diff --git a/samples/normal_bernoulli.png b/samples/normal_bernoulli.png index 752f5c0579e2053e608713560090980dbb1ad58f..e8d5183f19a0707e200267b12c6469980a5accd5 100644 GIT binary patch literal 23677 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 PVpoBa@=__17jOJOD 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 XnU7%UTWwn03F-7n`4b6Jmu~()49~K@ diff --git a/samples/simple_itpp.png b/samples/simple_itpp.png index bcf754af3bca7552658f1f88dfe94c8ca219c520..4d23ca120a244d6eef41de7c74397bdbbdcab31d 100644 GIT binary patch literal 17613 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 A3;+NC literal 17629 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 A6#xJL diff --git a/samples/student.png b/samples/student.png index 6c369a717d68001d92ab44dcce0ca91eb6bdc08c..25e674e8c91f7b5692ce6b1167f499ddedb60096 100644 GIT binary patch literal 16701 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