Initial zoo with bidirectional impl
commit
47c9ad1666
|
@ -0,0 +1,4 @@
|
||||||
|
/_build
|
||||||
|
/.direnv
|
||||||
|
/.vscode
|
||||||
|
/node_modules
|
|
@ -0,0 +1,44 @@
|
||||||
|
STEAL THIS LICENSE
|
||||||
|
|
||||||
|
Copyright 2022 mat ess. All rights reserved.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This is not a valid license file, this is simply a note to thieves.
|
||||||
|
|
||||||
|
You can probably get away with stealing this work.
|
||||||
|
|
||||||
|
1. I am unlikely to find out. Who the utter fuck has time to spend all day
|
||||||
|
scanning the net to find copyright infractions. Hell, you might not even
|
||||||
|
publish it on the net in the first place. That just sounds boring as fuck.
|
||||||
|
I have better things to do like touching some fucking grass or hugging cute
|
||||||
|
people.
|
||||||
|
|
||||||
|
2. Thanks to Late Stage Capitalism™, I am unlikely to have the means for a
|
||||||
|
lengthy and drawn out court battle, you could easily win that through
|
||||||
|
attrition. I mean as I write this note I’m effectively homeless and bed
|
||||||
|
surfing. Literally the last thing I care about right now is someone using
|
||||||
|
my shit.
|
||||||
|
|
||||||
|
3. You and I both know that since I wrote this, I probably do not give a
|
||||||
|
flying shit if you create derivatives from my work or not.
|
||||||
|
|
||||||
|
However, you also can’t trust that any of those things are true.
|
||||||
|
|
||||||
|
I could be lying to you.
|
||||||
|
|
||||||
|
We both know it’s probably morally good for you to build off my work, and
|
||||||
|
redistribute it as far as it can go, humans have been doing this since
|
||||||
|
before we figured out fire. Plagiarism is a lie we tell ourselves to try to
|
||||||
|
constrain and suppress entropy. We like to pretend someone in 200 years
|
||||||
|
will give a shit about something we wrote while hyperfocusing and probably
|
||||||
|
high as shit.
|
||||||
|
|
||||||
|
But maybe if you’re a fucking nazi or a piggy or a xenophobic snitch, I
|
||||||
|
actually do care and will prosecute you to the fullest extent of the law.
|
||||||
|
Maybe I change my mind one day. Or something else.
|
||||||
|
|
||||||
|
If you’re reading this file, you probably want to use my shit anyway,
|
||||||
|
right? You wanna build it, you wanna package it, you wanna build off it.
|
||||||
|
|
||||||
|
So will you obey the law? Or will you do what is morally correct?
|
|
@ -0,0 +1,5 @@
|
||||||
|
# lang zoo
|
||||||
|
|
||||||
|
- bidirectional
|
||||||
|
- implementation of ["Complete and Easy Bidirectional Typechecking
|
||||||
|
for Higher-Rank Polymorphism"](https://www.cl.cam.ac.uk/~nk480/bidir.pdf)
|
|
@ -0,0 +1,185 @@
|
||||||
|
module StringSet = Set.Make (String)
|
||||||
|
|
||||||
|
type name = string
|
||||||
|
|
||||||
|
type type' =
|
||||||
|
(* 1 *)
|
||||||
|
| Unit
|
||||||
|
(* x *)
|
||||||
|
| Variable of name
|
||||||
|
(* ^x *)
|
||||||
|
| Existential of name
|
||||||
|
(* forall a. A *)
|
||||||
|
| ForAll of name * type'
|
||||||
|
(* A -> B *)
|
||||||
|
| Function of type' * type'
|
||||||
|
|
||||||
|
let rec free_variables = function
|
||||||
|
| Unit -> StringSet.empty
|
||||||
|
| Variable x | Existential x -> StringSet.singleton x
|
||||||
|
| ForAll (alpha, a) -> StringSet.remove alpha (free_variables a)
|
||||||
|
| Function (a, b) -> StringSet.inter (free_variables a) (free_variables b)
|
||||||
|
|
||||||
|
let occurs_check name type' = free_variables type' |> StringSet.mem name |> not
|
||||||
|
|
||||||
|
let rec is_monotype = function
|
||||||
|
| Unit | Variable _ | Existential _ -> true
|
||||||
|
| ForAll _ -> false
|
||||||
|
| Function (a, b) -> is_monotype a && is_monotype b
|
||||||
|
|
||||||
|
let rec replace name with_type in_type =
|
||||||
|
match in_type with
|
||||||
|
| (Variable n | Existential n) when n = name -> with_type
|
||||||
|
| Variable _ | Existential _ | Unit -> in_type
|
||||||
|
| ForAll (alpha, _) when name = alpha -> in_type
|
||||||
|
| ForAll (alpha, a) -> ForAll (alpha, replace name with_type a)
|
||||||
|
| Function (a, b) ->
|
||||||
|
Function (replace name with_type a, replace name with_type b)
|
||||||
|
|
||||||
|
type term =
|
||||||
|
(* () *)
|
||||||
|
| Unit
|
||||||
|
(* x *)
|
||||||
|
| Variable of name
|
||||||
|
(* \x. e *)
|
||||||
|
| Lambda of name * term
|
||||||
|
(* f x *)
|
||||||
|
| Application of term * term
|
||||||
|
(* x : T *)
|
||||||
|
| Annotation of term * type'
|
||||||
|
|
||||||
|
type judgment =
|
||||||
|
(* universally quantified *)
|
||||||
|
| ForAll of name
|
||||||
|
| Assume of name * type'
|
||||||
|
(* existentially quantified *)
|
||||||
|
| Exists of name
|
||||||
|
| Solved of name * type'
|
||||||
|
(* marker *)
|
||||||
|
| Marker of name
|
||||||
|
|
||||||
|
type context = judgment list
|
||||||
|
|
||||||
|
let existentials =
|
||||||
|
List.fold_left
|
||||||
|
(fun vars j ->
|
||||||
|
match j with
|
||||||
|
| Exists n | Solved (n, _) ->
|
||||||
|
StringSet.singleton n |> StringSet.union vars
|
||||||
|
| _ -> vars)
|
||||||
|
StringSet.empty
|
||||||
|
|
||||||
|
let in_existentials name gamma = existentials gamma |> StringSet.mem name
|
||||||
|
|
||||||
|
exception TypeError of string
|
||||||
|
|
||||||
|
let lookup_name context name =
|
||||||
|
let find_name = function
|
||||||
|
| (ForAll n | Assume (n, _) | Exists n | Solved (n, _)) when n = name ->
|
||||||
|
true
|
||||||
|
| _ -> false
|
||||||
|
in
|
||||||
|
List.find find_name context
|
||||||
|
|
||||||
|
let rec drop_marker marker context =
|
||||||
|
match context with
|
||||||
|
| [] -> raise (TypeError "tried to drop missing marker")
|
||||||
|
| top :: rest when top = marker -> rest
|
||||||
|
| _ :: rest -> drop_marker marker rest
|
||||||
|
|
||||||
|
let solve name solution context =
|
||||||
|
let solve' = function
|
||||||
|
| Exists n when n = name -> Solved (n, solution)
|
||||||
|
| j -> j
|
||||||
|
in
|
||||||
|
List.map solve' context
|
||||||
|
|
||||||
|
let rec substitute gamma (a : type') =
|
||||||
|
match a with
|
||||||
|
| Unit | Variable _ -> a
|
||||||
|
| Existential alpha -> (
|
||||||
|
match lookup_name gamma alpha with
|
||||||
|
| Exists _ -> a
|
||||||
|
| Solved (_, tau) -> substitute gamma tau
|
||||||
|
| _ -> raise (TypeError "unbound existential"))
|
||||||
|
| Function (a, b) -> Function (substitute gamma a, substitute gamma b)
|
||||||
|
| ForAll (alpha, a) -> ForAll (alpha, substitute gamma a)
|
||||||
|
|
||||||
|
let mk_fresh_generator prefix =
|
||||||
|
let counter = ref 0 in
|
||||||
|
fun base ->
|
||||||
|
let output = prefix ^ base ^ string_of_int !counter in
|
||||||
|
counter := !counter + 1;
|
||||||
|
output
|
||||||
|
|
||||||
|
let fresh_variable = mk_fresh_generator "$"
|
||||||
|
let fresh_type_variable = mk_fresh_generator "'"
|
||||||
|
|
||||||
|
let rec instantiate_left alpha a gamma =
|
||||||
|
match a with
|
||||||
|
| _ when is_monotype a -> solve alpha a gamma
|
||||||
|
| Existential beta -> solve beta (Existential alpha) gamma
|
||||||
|
| Function (a1, a2) ->
|
||||||
|
let alpha_1 = fresh_type_variable alpha in
|
||||||
|
let alpha_2 = fresh_type_variable alpha in
|
||||||
|
let theta =
|
||||||
|
instantiate_right a1 alpha_1
|
||||||
|
(Solved (alpha, Function (Variable alpha_1, Variable alpha_2))
|
||||||
|
:: Exists alpha_1 :: Exists alpha_2 :: gamma)
|
||||||
|
in
|
||||||
|
instantiate_left alpha_2 (substitute theta a2) theta
|
||||||
|
| ForAll (beta, b) ->
|
||||||
|
let beta' = fresh_type_variable beta in
|
||||||
|
instantiate_left alpha b (ForAll beta' :: gamma)
|
||||||
|
|> drop_marker (ForAll beta')
|
||||||
|
| _ -> raise (TypeError "bad left instantiation")
|
||||||
|
|
||||||
|
and instantiate_right a alpha gamma =
|
||||||
|
match a with
|
||||||
|
| _ when is_monotype a -> solve alpha a gamma
|
||||||
|
| Existential beta -> solve beta (Existential alpha) gamma
|
||||||
|
| Function (a1, a2) ->
|
||||||
|
let alpha_1 = fresh_type_variable alpha in
|
||||||
|
let alpha_2 = fresh_type_variable alpha in
|
||||||
|
let theta =
|
||||||
|
instantiate_left alpha_1 a1
|
||||||
|
(Solved (alpha, Function (Variable alpha_1, Variable alpha_2))
|
||||||
|
:: Exists alpha_1 :: Exists alpha_2 :: gamma)
|
||||||
|
in
|
||||||
|
instantiate_right (substitute theta a2) alpha_2 theta
|
||||||
|
| ForAll (beta, b) ->
|
||||||
|
let beta' = fresh_type_variable beta in
|
||||||
|
instantiate_right b alpha (ForAll beta' :: gamma)
|
||||||
|
|> drop_marker (ForAll beta')
|
||||||
|
| _ -> raise (TypeError "bad right instantiation")
|
||||||
|
|
||||||
|
let rec subtype (a : type') (b : type') gamma =
|
||||||
|
match (a, b) with
|
||||||
|
| Variable a, Variable b when a = b -> gamma
|
||||||
|
| Unit, Unit -> gamma
|
||||||
|
| Existential a, Existential b when a = b && in_existentials a gamma -> gamma
|
||||||
|
| Function (a1, a2), Function (b1, b2) ->
|
||||||
|
let theta = subtype b1 a1 gamma in
|
||||||
|
subtype (substitute theta a2) (substitute theta b2) theta
|
||||||
|
| ForAll (alpha, a), _ ->
|
||||||
|
let alpha' = fresh_type_variable alpha in
|
||||||
|
let gamma = Exists alpha' :: Marker alpha' :: gamma in
|
||||||
|
let a' = replace alpha (Existential alpha') a in
|
||||||
|
subtype a' b gamma |> drop_marker (Marker alpha')
|
||||||
|
| _, ForAll (beta, b) ->
|
||||||
|
let beta' = fresh_type_variable beta in
|
||||||
|
let b' = replace beta (Variable beta') b in
|
||||||
|
subtype a b' (ForAll beta' :: gamma) |> drop_marker (ForAll beta')
|
||||||
|
| Existential alpha, _ when occurs_check alpha b ->
|
||||||
|
instantiate_left alpha b gamma
|
||||||
|
| _, Existential beta when occurs_check beta a ->
|
||||||
|
instantiate_right a beta gamma
|
||||||
|
| _ -> raise (TypeError "invalid subtype relation")
|
||||||
|
|
||||||
|
let rec check (e : term) (a : type') gamma =
|
||||||
|
match (e, a) with _ -> raise (TypeError "bad check")
|
||||||
|
|
||||||
|
and synth (e : term) gamma = match e with _ -> raise (TypeError "bad synth")
|
||||||
|
|
||||||
|
and apply (a : type') (e : term) gamma =
|
||||||
|
match (a, e) with _ -> raise (TypeError "bad apply")
|
|
@ -0,0 +1,3 @@
|
||||||
|
(executable
|
||||||
|
(name bidirectional)
|
||||||
|
(public_name bidirectional))
|
|
@ -0,0 +1,19 @@
|
||||||
|
(lang dune 3.6)
|
||||||
|
|
||||||
|
(name lang_zoo)
|
||||||
|
|
||||||
|
(source
|
||||||
|
(uri https://git.mat.services/mat/lang-zoo))
|
||||||
|
|
||||||
|
(authors "mat ess")
|
||||||
|
|
||||||
|
(maintainers "mat ess")
|
||||||
|
|
||||||
|
(license LICENSE.txt)
|
||||||
|
|
||||||
|
(package
|
||||||
|
(name lang_zoo)
|
||||||
|
(description "programming language experiments")
|
||||||
|
(depends ocaml dune)
|
||||||
|
(tags
|
||||||
|
(plt programming language experiment)))
|
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1672616755,
|
||||||
|
"narHash": "sha256-dvwU2ORLpiP6ZMXL3CJ/qrqmtLBLF6VAc+Fois7Qfew=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "87673d7c13a799d95ce25ff5dc7b9e15f01af2ea",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1667395993,
|
||||||
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1672791794,
|
||||||
|
"narHash": "sha256-mqGPpGmwap0Wfsf3o2b6qHJW1w2kk/I6cGCGIU+3t6o=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9813adc7f7c0edd738c6bdd8431439688bb0cb3d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"dir": "lib",
|
||||||
|
"lastModified": 1672350804,
|
||||||
|
"narHash": "sha256-jo6zkiCabUBn3ObuKXHGqqORUMH27gYDIFFfLq5P4wg=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "677ed08a50931e38382dbef01cba08a8f7eac8f6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"dir": "lib",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ocaml": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1672791669,
|
||||||
|
"narHash": "sha256-tlH9qTn6MedVq71GtExiRrGnWAFhkQNq2I6YvG87iaQ=",
|
||||||
|
"owner": "nix-ocaml",
|
||||||
|
"repo": "nix-overlays",
|
||||||
|
"rev": "34b689e9189e63bc6e093e6fccc690519e985a94",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-ocaml",
|
||||||
|
"repo": "nix-overlays",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"ocaml": "ocaml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
{
|
||||||
|
description = "language zoo";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/master";
|
||||||
|
ocaml.url = "github:nix-ocaml/nix-overlays";
|
||||||
|
ocaml.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs@{ self, flake-parts, nixpkgs, ocaml }:
|
||||||
|
let
|
||||||
|
ocamlModule = {
|
||||||
|
config.perSystem = { system, ... }: {
|
||||||
|
config._module.args.pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [
|
||||||
|
ocaml.overlays.${system}
|
||||||
|
(_: prev: {
|
||||||
|
ocaml = prev.ocaml-ng.ocamlPackages_latest.ocaml;
|
||||||
|
ocamlPackages = prev.ocaml-ng.ocamlPackages_latest;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
|
imports = [ ocamlModule ];
|
||||||
|
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||||
|
perSystem = { config, self', inputs', pkgs, system, ... }: {
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
nativeBuildInputs = builtins.attrValues {
|
||||||
|
inherit (pkgs.ocamlPackages)
|
||||||
|
dune
|
||||||
|
ocaml
|
||||||
|
;
|
||||||
|
};
|
||||||
|
buildInputs = builtins.attrValues {
|
||||||
|
inherit (pkgs.ocamlPackages)
|
||||||
|
dune
|
||||||
|
merlin
|
||||||
|
ocaml
|
||||||
|
ocaml-lsp
|
||||||
|
ocamlformat
|
||||||
|
ocamlformat-rpc-lib
|
||||||
|
utop
|
||||||
|
;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue