Compare commits

...

8 commits

16 changed files with 416 additions and 332 deletions

View file

@ -18,11 +18,11 @@ To be perfectly clear, =λ= abstractions can be written with either "λ" or "fun
=Let= expressions have syntax shown below. =Let= expressions have syntax shown below.
#+begin_src #+begin_src
let ( (<ident> := <expr>) )+ in <expr> end let ( (<ident> (: <type>)? := <expr>) )+ in <expr> end
#+end_src #+end_src
Below is a more concrete example. Below is a more concrete example.
#+begin_src #+begin_src
let (a := (and_elim_l A (and B C) h)) let (a : A := (and_elim_l A (and B C) h))
(bc := (and_elim_r A (and B C) h)) (bc := (and_elim_r A (and B C) h))
(b := (and_elim_l B C bc)) (b := (and_elim_l B C bc))
(c := (and_elim_r B C bc)) (c := (and_elim_r B C bc))
@ -38,19 +38,20 @@ You can also directly bind functions. Here's an example.
#+end_src #+end_src
The syntax for binding functions is just like with definitions. The syntax for binding functions is just like with definitions.
Definitions have abstract syntax as shown below. Definitions and axioms have abstract syntax as shown below.
#+begin_src #+begin_src
<ident> (<ident> : <type>)* : <type>? := <term> | axiom; def <ident> (<ident> : <type>)* : <type>? := <term>;
axiom <ident> (<ident> : <type>)* : <type>;
#+end_src #+end_src
(The distinction between =<type>= and =<term>= is for emphasis; they are the exact same syntactic category.) Here's a couple definitions of the =const= function from above showing the options with the syntax, and a more complex example declaring functional extensionality as an axiom (assuming equality has been previously defined having type =eq : Π (A : *) → A → A → *=). Duplicate definitions are not normally allowed and will result in an error. (The distinction between =<type>= and =<term>= is purely for emphasis; they are the exact same syntactic category.) Here's a couple definitions of the =const= function from above showing the options with the syntax, and a more complex example declaring functional extensionality as an axiom (assuming equality has been previously defined having type =eq : Π (A : *) → A → A → *=). Duplicate definitions are not normally allowed and will result in an error.
#+begin_src #+begin_src
const := λ (α : *) ⇒ λ (β : *) ⇒ λ (x : α) => λ (y : β) => x; def const := λ (α : *) ⇒ λ (β : *) ⇒ λ (x : α) => λ (y : β) => x;
const : ∀ (α β : *), α → β → α := fun (α β : *) (x : α) (y : β) ⇒ x; def const : ∀ (α β : *), α → β → α := fun (α β : *) (x : α) (y : β) ⇒ x;
const (α β : *) (x : α) (y : β) : α := x; def const (α β : *) (x : α) (y : β) : α := x;
funext (A B : *) (f g : A → B) : (∀ (x : A), eq B (f x) (g x)) → eq (A → B) f g := axiom; axiom funext (A B : *) (f g : A → B) : (∀ (x : A), eq B (f x) (g x)) → eq (A → B) f g;
#+end_src #+end_src
Type ascriptions are optional. If included, =perga= will check to make sure your definition matches the ascription, and, if so, will remember the way your wrote the type when printing inferred types, which is particularly handy when using abbreviations for complex types. =perga= has no problem inferring the types of top-level definitions, as they are completely determined by the term, but I recommend including ascriptions most of the time, as they serve as a nice piece of documentation, help guide the implementation process, and make sure you are implementing the type you think you are. Type ascriptions are optional in both definitions and =let= bindings. If included, =perga= will check to make sure your definition matches the ascription, and, if so, will remember the way your wrote the type when printing inferred types, which is particularly handy when using abbreviations for complex types. =perga= has no problem inferring the types of top-level definitions, as they are completely determined by the term, but I recommend including ascriptions most of the time, as they serve as a nice piece of documentation, help guide the implementation process, and make sure you are implementing the type you think you are.
If the RHS of a definition is =axiom=, then =perga= will assume that the identifier is an inhabitant of the type ascribed to it (as such when using axioms, a type ascription is required). This allows you to use axioms. If the RHS of a definition is =axiom=, then =perga= will assume that the identifier is an inhabitant of the type ascribed to it (as such when using axioms, a type ascription is required). This allows you to use axioms.
@ -59,7 +60,7 @@ Line comments are =--= like in Haskell, and block comments are =[* *]= somewhat
There isn't a proper module system (yet), but you can include other files in a dumb, C preprocessor way by using =@include <filepath>= (NOTE: this unfortunately messes up line numbers in error messages). Filepaths are relative to the current file. Additionally, =@include= automatically keeps track of what has been included, so duplicate inclusions are skipped, meaning no include guards are necessary. There isn't a proper module system (yet), but you can include other files in a dumb, C preprocessor way by using =@include <filepath>= (NOTE: this unfortunately messes up line numbers in error messages). Filepaths are relative to the current file. Additionally, =@include= automatically keeps track of what has been included, so duplicate inclusions are skipped, meaning no include guards are necessary.
* Usage * Usage
Running =perga= without any arguments drops you into a basic repl. From here, you can type in definitions which =perga= will typecheck. Previous definitions are accessible in future definitions. The usual readline keybindings are available, including navigating history, which is saved between sessions (in =~/.cache/perga/history=). In the repl, you can enter ":q", press C-c, or press C-d to quit. Entering ":e" shows everything that has been defined along with their types. Entering ":t <ident>" prints the type of a particular identifier, while ":v <ident>" prints its value. Entering ":n <expr>" will fully normalize (including unfolding definitions) an expression, while ":w <expr>" will reduce it to weak head normal form. Finally ":l <filepath>" loads a file. Running =perga= without any arguments drops you into a basic repl. From here, you can type in definitions which =perga= will typecheck. Previous definitions are accessible in future definitions. The usual readline keybindings are available, including navigating history, which is saved between sessions (in =~/.cache/perga/history=). In the repl, you can enter ":q", press C-c, or press C-d to quit. Entering ":e" shows everything that has been defined along with their types. If you want to see the value of an identifier defined in the environment, you can enter ":v <ident>". Entering ":t <expr>" prints the type of an expression. Entering ":n <expr>" will fully normalize (including unfolding definitions) an expression, while ":w <expr>" will reduce it to weak head normal form. Finally ":l <filepath>" loads a file.
Here's an example session showing the capabilities of the repl. Here's an example session showing the capabilities of the repl.
#+begin_src text #+begin_src text
@ -160,7 +161,7 @@ This indicates that, when evaluating =Hxy Hx=, it was expecting something of typ
** Substantive ** Substantive
*** TODO Sections *** TODO Sections
Coq-style sections would be very handy, and probably /relatively/ easy to implement (compared to everything else on this todo list). Upon parsing a definition inside a section, will somehow need to look ahead to see what variables are used to see how I need to modify =binders=, or just make every definition require every section variable as an argument. Coq-style sections would be very handy, and probably /relatively/ easy to implement (compared to everything else on this todo list), especially now that we have an [[Multiple levels of AST][intermediate representation]].
*** TODO Inference *** TODO Inference
Not decidable, but I might be able to implement some basic unification algorithm, or switch to bidirectional type checking. This isn't super necessary though, I find leaving off the types of arguments to generally be a bad idea, but in some cases it can be handy, especially not at the top level. Not decidable, but I might be able to implement some basic unification algorithm, or switch to bidirectional type checking. This isn't super necessary though, I find leaving off the types of arguments to generally be a bad idea, but in some cases it can be handy, especially not at the top level.
@ -169,7 +170,7 @@ Not decidable, but I might be able to implement some basic unification algorithm
Much, much more useful than [[Inference][inference]], implicit arguments would be amazing. It also seems a lot more complicated, but any system for dealing with implicit arguments is far better than none. Much, much more useful than [[Inference][inference]], implicit arguments would be amazing. It also seems a lot more complicated, but any system for dealing with implicit arguments is far better than none.
*** TODO Module System *** TODO Module System
A proper module system would be wonderful. To me, ML style modules with structures, signatures, and functors seems like the right way to handle algebraic structures for a relatively simple language, rather than records (or, worse, a bunch of =and='s like I currently have; especially painful without [[Implicits][implicits]]) or type classes (probably much harder, but could be nicer), but any way of managing scope, importing files, etc. is a necessity. The [[https://www.cambridge.org/core/services/aop-cambridge-core/content/view/B573FA00832D55D4878863DE1725D90B/S0956796814000264a.pdf/f-ing-modules.pdf][F-ing modules]] paper is probably a good reference. A proper module system would be wonderful. To me, ML style modules with structures, signatures, and functors seems like the right way to handle algebraic structures for a relatively simple language, rather than records (or, worse, a bunch of =and='s like I currently have; especially painful without [[Implicits][implicits]]) or type classes (probably much harder, but could be nicer), but any way of managing scope, importing files, etc. is a necessity. The [[https://www.cambridge.org/core/services/aop-cambridge-core/content/view/B573FA00832D55D4878863DE1725D90B/S0956796814000264a.pdf/f-ing-modules.pdf][F-ing modules]] paper is probably a good reference. Now that I have an [[Multiple levels of AST][intermediate representation]], following in /F-ing modules/'s footsteps and implementing modules purely through elaboration should be possible.
*** DONE Universes? *** DONE Universes?
Not super necessary, but occasionally extremely useful. Could be fun, idk. Not super necessary, but occasionally extremely useful. Could be fun, idk.
@ -178,6 +179,8 @@ I was looking into bidirectional typing and came across a description of univers
Also, everything ends up impredicative (no =* : *=, but quantifying over =*i= still leaves you in =*i=), and my implementation of impredicativity feels a little sketchy. There might be paradoxes lurking. It would be easy to switch it over to being predicative, but, without inductive types or at least more built-in types, logical connectives can only be defined impredicatively, so that will have to wait until we have inductive definitions. Also, everything ends up impredicative (no =* : *=, but quantifying over =*i= still leaves you in =*i=), and my implementation of impredicativity feels a little sketchy. There might be paradoxes lurking. It would be easy to switch it over to being predicative, but, without inductive types or at least more built-in types, logical connectives can only be defined impredicatively, so that will have to wait until we have inductive definitions.
I may follow in Coq's footsteps and switch universe hierarchies to something like =* : □ : □₁ : □₂ : □₃ : ...=, where all the =□ᵢ= are predicative and =*= is impredicative (analogous to =Prop= and =Type i=). For now at least, we definitely need at least the lowest sort to be impredicative to allow for impredicative definitions of connectives.
*** TODO Universe polymorphism *** TODO Universe polymorphism
I have [[Universes?][universes]], but without universe polymorphism, they're basically useless, or at least I am unable to find a practical use for them. (When would you want to quantify over e.g. kinds specifically?) I have [[Universes?][universes]], but without universe polymorphism, they're basically useless, or at least I am unable to find a practical use for them. (When would you want to quantify over e.g. kinds specifically?)
@ -204,8 +207,8 @@ Probably a smart idea.
*** TODO More incremental parsing/typechecking *** TODO More incremental parsing/typechecking
Right now, if there's a failure, everything just stops immediately. More incremental parsing/typechecking could pave the way for more interactivity, e.g. development with holes, an LSP server, etc., not to mention better error messages. Right now, if there's a failure, everything just stops immediately. More incremental parsing/typechecking could pave the way for more interactivity, e.g. development with holes, an LSP server, etc., not to mention better error messages.
*** TODO Multiple levels of AST *** DONE Multiple levels of AST
Right now, the parsing and typechecking kind of happens all at once on a single syntax representation. As I add fancier and fancier elaboration, it might be a good idea to have multiple syntax representations. So we'd have one level of syntax representing what is actually in the file (modulo some easy elaboration like with function definitions), and through a series of transformations transform it into something like the current =Expr= type with all the information needed for typechecking and all the complex language features removed. Added a couple types representing an intermediate representation, as well as a module responsible for elaboration (basically a function from these intermediate types to =Expr=). This /drastically/ simplified the parser, now that it is not responsible for converting to de Bruijn indices, handling the environment, and type checking all in addition to parsing. However, now that type checking is out of the parser, we lost location information for errors, making [[Improve error message][better error messages]] much more important now. I have some ideas for getting location information (and more accurate location information, instead of always pointing to the end of the most relevant definition), which should drastically improve the error messages.
*** TODO Improve type checking algorithm *** TODO Improve type checking algorithm
I'm proud that I just kinda made up a working type checking algorithm, but it is clearly quite flawed. Even assuming no need to check beta equivalence, I'm pretty sure that this algorithm is approximately exponential in the length of a term, which is pretty terrible. It hasn't been a huge problem, but type checking just the term =R2_sub_R= in [[./examples/peano.pg]] takes about 1.5 seconds. Performance could easily be drastically improved with some memoization, but upgrading to an off-the-shelf bidirectional type checking algorithm seems like a good idea in general. Another problem with my current type checking algorithm is that it is very inflexible (e.g. adding optional return type ascriptions in functions, or type ascriptions in =let= bindings is currently impossible, while trivial to add with bidirectional type checking). I also have no idea how to add [[Inference][type inference]] or [[Implicits][implicits]] with how things are currently structured. A more flexible type checking algorithm, likely together with [[Multiple levels of AST][multiple levels of AST]], makes it seem more possible. I'm proud that I just kinda made up a working type checking algorithm, but it is clearly quite flawed. Even assuming no need to check beta equivalence, I'm pretty sure that this algorithm is approximately exponential in the length of a term, which is pretty terrible. It hasn't been a huge problem, but type checking just the term =R2_sub_R= in [[./examples/peano.pg]] takes about 1.5 seconds. Performance could easily be drastically improved with some memoization, but upgrading to an off-the-shelf bidirectional type checking algorithm seems like a good idea in general. Another problem with my current type checking algorithm is that it is very inflexible (e.g. adding optional return type ascriptions in functions, or type ascriptions in =let= bindings is currently impossible, while trivial to add with bidirectional type checking). I also have no idea how to add [[Inference][type inference]] or [[Implicits][implicits]] with how things are currently structured. A more flexible type checking algorithm, likely together with [[Multiple levels of AST][multiple levels of AST]], makes it seem more possible.

View file

@ -1,7 +1,7 @@
module Main where module Main where
import Eval (Env, emptyEnv) import qualified Data.Map.Strict as M
import Parser (handleFile) import Program (handleAndParseFile)
import Repl import Repl
main :: IO () main :: IO ()
@ -9,8 +9,8 @@ main = do
args <- getArgs args <- getArgs
case args of case args of
[] -> void repl [] -> void repl
files -> handleFiles emptyEnv files files -> handleFiles files
handleFiles :: Env -> [String] -> IO () handleFiles :: [String] -> IO ()
handleFiles _ [] = putTextLn "success!" handleFiles [] = putTextLn "success!"
handleFiles env (file : rest) = runExceptT (handleFile env file) >>= either putStrLn (`handleFiles` rest) handleFiles (file : rest) = runExceptT (handleAndParseFile M.empty file) >>= either putStrLn (const $ handleFiles rest)

View file

@ -4,10 +4,12 @@ import Check (findType)
import Data.List (stripPrefix) import Data.List (stripPrefix)
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Text (pack) import Data.Text (pack)
import Elaborator
import Errors (Result) import Errors (Result)
import Eval import Eval
import Expr import Expr
import Parser import Parser
import Program
import System.Console.Haskeline import System.Console.Haskeline
import System.Directory (createDirectoryIfMissing, getHomeDirectory) import System.Directory (createDirectoryIfMissing, getHomeDirectory)
import System.FilePath ((</>)) import System.FilePath ((</>))
@ -37,21 +39,22 @@ parseCommand (Just input)
handleInput :: Env -> String -> InputT IO Env handleInput :: Env -> String -> InputT IO Env
handleInput env input = handleInput env input =
let (res, env') = parseDefEmpty env (pack input) case parseDef "repl" (pack input) of
in case res of Left err -> outputStrLn err >> pure env
Left err -> outputStrLn err >> pure env' Right irDef -> case evalDef env irDef of
Right () -> pure env' Left err -> outputStrLn (toString err) >> pure env
Right env' -> pure env'
actOnParse :: Env -> String -> (Expr -> InputT IO ()) -> InputT IO () actOnParse :: String -> (Expr -> InputT IO ()) -> InputT IO ()
actOnParse env input action = case parseExpr env (pack input) of actOnParse input action = case parseExpr "repl" (pack input) of
Left err -> outputStrLn err Left err -> outputStrLn err
Right expr -> action expr Right expr -> action $ elaborate expr
printErrorOrResult :: Env -> (Expr -> ReaderT Env Result Expr) -> Expr -> InputT IO () printErrorOrResult :: Env -> (Expr -> ReaderT Env Result Expr) -> Expr -> InputT IO ()
printErrorOrResult env action expr = putTextLn $ either toText pretty $ runReaderT (action expr) env printErrorOrResult env action expr = putTextLn $ either toText pretty $ runReaderT (action expr) env
parseActPrint :: Env -> String -> (Expr -> ReaderT Env Result Expr) -> InputT IO () parseActPrint :: Env -> String -> (Expr -> ReaderT Env Result Expr) -> InputT IO ()
parseActPrint env input action = actOnParse env input (printErrorOrResult env action) parseActPrint env input action = actOnParse input (printErrorOrResult env action)
lookupAct :: Env -> String -> (Definition -> InputT IO ()) -> InputT IO () lookupAct :: Env -> String -> (Definition -> InputT IO ()) -> InputT IO ()
lookupAct env input action = maybe (outputStrLn $ "'" ++ input ++ "' unbound") action $ M.lookup (pack input) env lookupAct env input action = maybe (outputStrLn $ "'" ++ input ++ "' unbound") action $ M.lookup (pack input) env
@ -75,5 +78,5 @@ repl = do
Just (ValueQuery input) -> lookupAct env input (putTextLn . pretty . _val) >> loop env Just (ValueQuery input) -> lookupAct env input (putTextLn . pretty . _val) >> loop env
Just (Normalize input) -> parseActPrint env input normalize >> loop env Just (Normalize input) -> parseActPrint env input normalize >> loop env
Just (WeakNormalize input) -> parseActPrint env input whnf >> loop env Just (WeakNormalize input) -> parseActPrint env input whnf >> loop env
Just (LoadFile filename) -> lift (runExceptT $ handleFile env filename) >>= either ((>> loop env) . outputStrLn) loop Just (LoadFile filename) -> lift (runExceptT $ handleAndParseFile env filename) >>= either ((>> loop env) . outputStrLn) loop
Just (Input input) -> handleInput env input >>= loop Just (Input input) -> handleInput env input >>= loop

View file

@ -13,64 +13,64 @@
-- I'd always strongly recommend including the type ascriptions for theorems -- I'd always strongly recommend including the type ascriptions for theorems
-- a unary operation on a set `A` is a function `A -> A` -- a unary operation on a set `A` is a function `A -> A`
unop (A : *) := A -> A; def unop (A : *) := A -> A;
-- a binary operation on a set `A` is a function `A -> A -> A` -- a binary operation on a set `A` is a function `A -> A -> A`
binop (A : *) := A -> A -> A; def binop (A : *) := A -> A -> A;
-- a binary operation is associative if ... -- a binary operation is associative if ...
assoc (A : *) (op : binop A) := def assoc (A : *) (op : binop A) :=
forall (a b c : A), eq A (op a (op b c)) (op (op a b) c); forall (a b c : A), eq A (op a (op b c)) (op (op a b) c);
-- an element `e : A` is a left identity with respect to binop `op` if `∀ a, e * a = a` -- an element `e : A` is a left identity with respect to binop `op` if `∀ a, e * a = a`
id_l (A : *) (op : binop A) (e : A) := def id_l (A : *) (op : binop A) (e : A) :=
forall (a : A), eq A (op e a) a; forall (a : A), eq A (op e a) a;
-- likewise for right identity -- likewise for right identity
id_r (A : *) (op : binop A) (e : A) := def id_r (A : *) (op : binop A) (e : A) :=
forall (a : A), eq A (op a e) a; forall (a : A), eq A (op a e) a;
-- an element is an identity element if it is both a left and right identity -- an element is an identity element if it is both a left and right identity
id (A : *) (op : binop A) (e : A) := and (id_l A op e) (id_r A op e); def id (A : *) (op : binop A) (e : A) := and (id_l A op e) (id_r A op e);
-- b is a left inverse for a if `b * a = e` -- b is a left inverse for a if `b * a = e`
-- NOTE: we don't require `e` to be an identity in this definition. -- NOTE: we don't require `e` to be an identity in this definition.
-- this definition is purely for convenience's sake -- this definition is purely for convenience's sake
inv_l (A : *) (op : binop A) (e : A) (a b : A) := eq A (op b a) e; def inv_l (A : *) (op : binop A) (e : A) (a b : A) := eq A (op b a) e;
-- likewise for right inverse -- likewise for right inverse
inv_r (A : *) (op : binop A) (e : A) (a b : A) := eq A (op a b) e; def inv_r (A : *) (op : binop A) (e : A) (a b : A) := eq A (op a b) e;
-- and full-on inverse -- and full-on inverse
inv (A : *) (op : binop A) (e : A) (a b : A) := and (inv_l A op e a b) (inv_r A op e a b); def inv (A : *) (op : binop A) (e : A) (a b : A) := and (inv_l A op e a b) (inv_r A op e a b);
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
-- | ALGEBRAIC STRUCTURES | -- | ALGEBRAIC STRUCTURES |
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
-- a set `S` with binary operation `op` is a semigroup if its operation is associative -- a set `S` with binary operation `op` is a semigroup if its operation is associative
semigroup (S : *) (op : binop S) : * := assoc S op; def semigroup (S : *) (op : binop S) : * := assoc S op;
-- a set `M` with binary operation `op` and element `e` is a monoid -- a set `M` with binary operation `op` and element `e` is a monoid
monoid (M : *) (op : binop M) (e : M) : * := def monoid (M : *) (op : binop M) (e : M) : * :=
and (semigroup M op) (id M op e); and (semigroup M op) (id M op e);
-- some "getters" for `monoid` so we don't have to do a bunch of very verbose -- some "getters" for `monoid` so we don't have to do a bunch of very verbose
-- and-eliminations every time we want to use something -- and-eliminations every time we want to use something
id_lm (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) : id_l M op e := def id_lm (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) : id_l M op e :=
and_elim_l (id_l M op e) (id_r M op e) and_elim_l (id_l M op e) (id_r M op e)
(and_elim_r (semigroup M op) (id M op e) Hmonoid); (and_elim_r (semigroup M op) (id M op e) Hmonoid);
id_rm (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) : id_r M op e := def id_rm (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) : id_r M op e :=
and_elim_r (id_l M op e) (id_r M op e) and_elim_r (id_l M op e) (id_r M op e)
(and_elim_r (semigroup M op) (id M op e) Hmonoid); (and_elim_r (semigroup M op) (id M op e) Hmonoid);
assoc_m (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) : assoc M op := def assoc_m (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) : assoc M op :=
and_elim_l (semigroup M op) (id M op e) Hmonoid; and_elim_l (semigroup M op) (id M op e) Hmonoid;
-- now we can prove that, for any monoid, if `a` is a left identity, then it -- now we can prove that, for any monoid, if `a` is a left identity, then it
-- must be "the" identity -- must be "the" identity
monoid_id_l_implies_identity def monoid_id_l_implies_identity
(M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e)
(a : M) (H : id_l M op a) : eq M a e := (a : M) (H : id_l M op a) : eq M a e :=
-- WTS a = a * e = e -- WTS a = a * e = e
@ -86,7 +86,7 @@ monoid_id_l_implies_identity
(H e); (H e);
-- the analogous result for right identities -- the analogous result for right identities
monoid_id_r_implies_identity def monoid_id_r_implies_identity
(M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e) (M : *) (op : binop M) (e : M) (Hmonoid : monoid M op e)
(a : M) (H : id_r M op a) : eq M a e := (a : M) (H : id_r M op a) : eq M a e :=
-- this time, we'll show `a = e * a = e` -- this time, we'll show `a = e * a = e`
@ -102,36 +102,36 @@ monoid_id_r_implies_identity
-- groups are just monoids with inverses -- groups are just monoids with inverses
has_inverses (G : *) (op : binop G) (e : G) (i : unop G) : * := def has_inverses (G : *) (op : binop G) (e : G) (i : unop G) : * :=
forall (a : G), inv G op e a (i a); forall (a : G), inv G op e a (i a);
group (G : *) (op : binop G) (e : G) (i : unop G) : * := def group (G : *) (op : binop G) (e : G) (i : unop G) : * :=
and (monoid G op e) and (monoid G op e)
(has_inverses G op e i); (has_inverses G op e i);
-- more "getters" -- more "getters"
monoid_g (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : monoid G op e := def monoid_g (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : monoid G op e :=
and_elim_l (monoid G op e) (has_inverses G op e i) Hgroup; and_elim_l (monoid G op e) (has_inverses G op e i) Hgroup;
assoc_g (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : assoc G op := def assoc_g (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : assoc G op :=
assoc_m G op e (monoid_g G op e i Hgroup); assoc_m G op e (monoid_g G op e i Hgroup);
id_lg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : id_l G op e := def id_lg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : id_l G op e :=
id_lm G op e (and_elim_l (monoid G op e) (has_inverses G op e i) Hgroup); id_lm G op e (and_elim_l (monoid G op e) (has_inverses G op e i) Hgroup);
id_rg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : id_r G op e := def id_rg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : id_r G op e :=
id_rm G op e (and_elim_l (monoid G op e) (has_inverses G op e i) Hgroup); id_rm G op e (and_elim_l (monoid G op e) (has_inverses G op e i) Hgroup);
inv_g (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : forall (a : G), inv G op e a (i a) := def inv_g (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) : forall (a : G), inv G op e a (i a) :=
and_elim_r (monoid G op e) (has_inverses G op e i) Hgroup; and_elim_r (monoid G op e) (has_inverses G op e i) Hgroup;
inv_lg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) (a : G) : inv_l G op e a (i a) := def inv_lg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) (a : G) : inv_l G op e a (i a) :=
and_elim_l (inv_l G op e a (i a)) (inv_r G op e a (i a)) (inv_g G op e i Hgroup a); and_elim_l (inv_l G op e a (i a)) (inv_r G op e a (i a)) (inv_g G op e i Hgroup a);
inv_rg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) (a : G) : inv_r G op e a (i a) := def inv_rg (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) (a : G) : inv_r G op e a (i a) :=
and_elim_r (inv_l G op e a (i a)) (inv_r G op e a (i a)) (inv_g G op e i Hgroup a); and_elim_r (inv_l G op e a (i a)) (inv_r G op e a (i a)) (inv_g G op e i Hgroup a);
left_inv_unique (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) (a b : G) (h : inv_l G op e a b) : eq G b (i a) := def left_inv_unique (G : *) (op : binop G) (e : G) (i : unop G) (Hgroup : group G op e i) (a b : G) (h : inv_l G op e a b) : eq G b (i a) :=
-- b = b * e -- b = b * e
-- = b * (a * a^-1) -- = b * (a * a^-1)
-- = (b * a) * a^-1 -- = (b * a) * a^-1

View file

@ -2,23 +2,23 @@
-- excluded middle! -- excluded middle!
-- P ~P -- P ~P
em (P : *) : or (P) (not P) := axiom; axiom em (P : *) : or P (not P);
-- ~~P => P -- ~~P => P
dne (P : *) (nnp : not (not P)) : P := def dne (P : *) (nnp : not (not P)) : P :=
or_elim P (not P) P (em P) or_elim P (not P) P (em P)
(fun (p : P) => p) (fun (p : P) => p)
(fun (np : not P) => nnp np P); (fun (np : not P) => nnp np P);
-- ((P => Q) => P) => P -- ((P => Q) => P) => P
peirce (P Q : *) (h : (P -> Q) -> P) : P := def peirce (P Q : *) (h : (P -> Q) -> P) : P :=
or_elim P (not P) P (em P) or_elim P (not P) P (em P)
(fun (p : P) => p) (fun (p : P) => p)
(fun (np : not P) => h (fun (p : P) => np p Q)); (fun (np : not P) => h (fun (p : P) => np p Q));
-- ~(A ∧ B) => ~A ~B -- ~(A ∧ B) => ~A ~B
de_morgan4 (A B : *) (h : not (and A B)) : or (not A) (not B) := def de_morgan4 (A B : *) (h : not (and A B)) : or (not A) (not B) :=
or_elim A (not A) (or (not A) (not B)) (em A) or_elim A (not A) (or (not A) (not B)) (em A)
(fun (a : A) => (fun (a : A) =>
or_elim B (not B) (or (not A) (not B)) (em B) or_elim B (not B) (or (not A) (not B)) (em B)

View file

@ -1,22 +1,24 @@
@include logic.pg
-- without recursion, computation is kind of hard -- without recursion, computation is kind of hard
-- we can, however, define natural numbers using the Church encoding and do -- we can, however, define natural numbers using the Church encoding and do
-- computation with them -- computation with them
-- the natural number `n` is encoded as the function taking any function -- the natural number `n` is encoded as the function taking any function
-- `A -> A` and repeating it `n` times -- `A -> A` and repeating it `n` times
nat : * := forall (A : *), (A -> A) -> A -> A; def nat : * := forall (A : *), (A -> A) -> A -> A;
-- zero is the constant function -- zero is the constant function
zero : nat := fun (A : *) (f : A -> A) (x : A) => x; def zero : nat := fun (A : *) (f : A -> A) (x : A) => x;
-- `suc n` composes one more `f` than `n` -- `suc n` composes one more `f` than `n`
suc : nat -> nat := fun (n : nat) (A : *) (f : A -> A) (x : A) => f ((n A f) x); def suc : nat -> nat := fun (n : nat) (A : *) (f : A -> A) (x : A) => f ((n A f) x);
-- addition can be encoded as usual -- addition can be encoded as usual
plus : nat -> nat -> nat := fun (n m : nat) (A : *) (f : A -> A) (x : A) => (m A f) (n A f x); def plus : nat -> nat -> nat := fun (n m : nat) (A : *) (f : A -> A) (x : A) => (m A f) (n A f x);
-- likewise for multiplication -- likewise for multiplication
times : nat -> nat -> nat := fun (n m : nat) (A : *) (f : A -> A) (x : A) => (m A (n A f)) x; def times : nat -> nat -> nat := fun (n m : nat) (A : *) (f : A -> A) (x : A) => (m A (n A f)) x;
-- unforunately, it is impossible to prove induction on Church numerals, -- unforunately, it is impossible to prove induction on Church numerals,
-- which makes it really hard to prove standard theorems, or do anything really. -- which makes it really hard to prove standard theorems, or do anything really.
@ -24,16 +26,16 @@ times : nat -> nat -> nat := fun (n m : nat) (A : *) (f : A -> A) (x : A) => (m
-- but we can still do computations with Church numerals -- but we can still do computations with Church numerals
-- first some abbreviations for numbers will be handy -- first some abbreviations for numbers will be handy
one : nat := suc zero; def one : nat := suc zero;
two : nat := suc one; def two : nat := suc one;
three : nat := suc two; def three : nat := suc two;
four : nat := suc three; def four : nat := suc three;
five : nat := suc four; def five : nat := suc four;
six : nat := suc five; def six : nat := suc five;
seven : nat := suc six; def seven : nat := suc six;
eight : nat := suc seven; def eight : nat := suc seven;
nine : nat := suc eight; def nine : nat := suc eight;
ten : nat := suc nine; def ten : nat := suc nine;
-- now we can do a bunch of computations, even at the type level -- now we can do a bunch of computations, even at the type level
-- the way we can do this is by defining equality (see <examples/logic.pg> for -- the way we can do this is by defining equality (see <examples/logic.pg> for
@ -42,23 +44,12 @@ ten : nat := suc nine;
-- then perga will only accept our proof if `plus one one` and `two` are beta -- then perga will only accept our proof if `plus one one` and `two` are beta
-- equivalent -- equivalent
-- first we do need a definition of equality
eq (A : *) (x y : A) := forall (P : A -> *), P x -> P y;
eq_refl (A : *) (x : A) : eq A x x := fun (P : A -> *) (Hx : P x) => Hx;
eq_sym (A : *) (x y : A) (Hxy : eq A x y) : eq A y x := fun (P : A -> *) (Hy : P y) =>
Hxy (fun (z : A) => P z -> P x) (fun (Hx : P x) => Hx) Hy;
eq_trans (A : *) (x y z : A) (Hxy : eq A x y) (Hyz : eq A y z) : eq A x z := fun (P : A -> *) (Hx : P x) =>
Hyz P (Hxy P Hx);
eq_cong (A B : *) (x y : A) (f : A -> B) (H : eq A x y) : eq B (f x) (f y) :=
fun (P : B -> *) (Hfx : P (f x)) =>
H (fun (a : A) => P (f a)) Hfx;
-- since `plus one one` and `two` are beta-equivalent, `eq_refl nat two` is a -- since `plus one one` and `two` are beta-equivalent, `eq_refl nat two` is a
-- proof that `1 + 1 = 2` -- proof that `1 + 1 = 2`
one_plus_one_is_two : eq nat (plus one one) two := eq_refl nat two; def one_plus_one_is_two : eq nat (plus one one) two := eq_refl nat two;
-- we can likewise compute 2 + 2 -- we can likewise compute 2 + 2
two_plus_two_is_four : eq nat (plus two two) four := eq_refl nat four; def two_plus_two_is_four : eq nat (plus two two) four := eq_refl nat four;
-- even multiplication works -- even multiplication works
two_times_five_is_ten : eq nat (times two five) ten := eq_refl nat ten; def two_times_five_is_ten : eq nat (times two five) ten := eq_refl nat ten;

View file

@ -1,44 +1,44 @@
-- False -- False
false : * := forall (A : *), A; def false : * := forall (A : *), A;
-- no introduction rule -- no introduction rule
-- elimination rule -- elimination rule
false_elim (A : *) (contra : false) : A := contra A; def false_elim (A : *) (contra : false) : A := contra A;
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
-- Negation -- Negation
not (A : *) : * := A -> false; def not (A : *) : * := A -> false;
-- introduction rule (kinda just the definition) -- introduction rule (kinda just the definition)
not_intro (A : *) (h : A -> false) : not A := h; def not_intro (A : *) (h : A -> false) : not A := h;
-- elimination rule -- elimination rule
not_elim (A B : *) (a : A) (na : not A) : B := na a B; def not_elim (A B : *) (a : A) (na : not A) : B := na a B;
-- can introduce double negation (can't eliminate it as that isn't constructive) -- can introduce double negation (can't eliminate it as that isn't constructive)
double_neg_intro (A : *) (a : A) : not (not A) := def double_neg_intro (A : *) (a : A) : not (not A) :=
fun (notA : not A) => notA a; fun (notA : not A) => notA a;
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
-- Conjunction -- Conjunction
and (A B : *) : * := forall (C : *), (A -> B -> C) -> C; def and (A B : *) : * := forall (C : *), (A -> B -> C) -> C;
-- introduction rule -- introduction rule
and_intro (A B : *) (a : A) (b : B) : and A B := def and_intro (A B : *) (a : A) (b : B) : and A B :=
fun (C : *) (H : A -> B -> C) => H a b; fun (C : *) (H : A -> B -> C) => H a b;
-- left elimination rule -- left elimination rule
and_elim_l (A B : *) (ab : and A B) : A := def and_elim_l (A B : *) (ab : and A B) : A :=
ab A (fun (a : A) (b : B) => a); ab A (fun (a : A) (b : B) => a);
-- right elimination rule -- right elimination rule
and_elim_r (A B : *) (ab : and A B) : B := def and_elim_r (A B : *) (ab : and A B) : B :=
ab B (fun (a : A) (b : B) => b); ab B (fun (a : A) (b : B) => b);
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
@ -46,18 +46,18 @@ and_elim_r (A B : *) (ab : and A B) : B :=
-- Disjunction -- Disjunction
-- 2nd order disjunction -- 2nd order disjunction
or (A B : *) : * := forall (C : *), (A -> C) -> (B -> C) -> C; def or (A B : *) : * := forall (C : *), (A -> C) -> (B -> C) -> C;
-- left introduction rule -- left introduction rule
or_intro_l (A B : *) (a : A) : or A B := def or_intro_l (A B : *) (a : A) : or A B :=
fun (C : *) (ha : A -> C) (hb : B -> C) => ha a; fun (C : *) (ha : A -> C) (hb : B -> C) => ha a;
-- right introduction rule -- right introduction rule
or_intro_r (A B : *) (b : B) : or A B := def or_intro_r (A B : *) (b : B) : or A B :=
fun (C : *) (ha : A -> C) (hb : B -> C) => hb b; fun (C : *) (ha : A -> C) (hb : B -> C) => hb b;
-- elimination rule (kinda just the definition) -- elimination rule (kinda just the definition)
or_elim (A B C : *) (ab : or A B) (ha : A -> C) (hb : B -> C) : C := def or_elim (A B C : *) (ab : or A B) (ha : A -> C) (hb : B -> C) : C :=
ab C ha hb; ab C ha hb;
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
@ -65,14 +65,14 @@ or_elim (A B C : *) (ab : or A B) (ha : A -> C) (hb : B -> C) : C :=
-- Existential -- Existential
-- 2nd order existential -- 2nd order existential
exists (A : *) (P : A -> *) : * := forall (C : *), (forall (x : A), P x -> C) -> C; def exists (A : *) (P : A -> *) : * := forall (C : *), (forall (x : A), P x -> C) -> C;
-- introduction rule -- introduction rule
exists_intro (A : *) (P : A -> *) (a : A) (h : P a) : exists A P := def exists_intro (A : *) (P : A -> *) (a : A) (h : P a) : exists A P :=
fun (C : *) (g : forall (x : A), P x -> C) => g a h; fun (C : *) (g : forall (x : A), P x -> C) => g a h;
-- elimination rule (kinda just the definition) -- elimination rule (kinda just the definition)
exists_elim (A B : *) (P : A -> *) (ex_a : exists A P) (h : forall (a : A), P a -> B) : B := def exists_elim (A B : *) (P : A -> *) (ex_a : exists A P) (h : forall (a : A), P a -> B) : B :=
ex_a B h; ex_a B h;
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
@ -80,34 +80,34 @@ exists_elim (A B : *) (P : A -> *) (ex_a : exists A P) (h : forall (a : A), P a
-- Universal -- Universal
-- 2nd order universal (just ∏, including it for completeness) -- 2nd order universal (just ∏, including it for completeness)
all (A : *) (P : A -> *) : * := forall (a : A), P a; def all (A : *) (P : A -> *) : * := forall (a : A), P a;
-- introduction rule -- introduction rule
all_intro (A : *) (P : A -> *) (h : forall (a : A), P a) : all A P := h; def all_intro (A : *) (P : A -> *) (h : forall (a : A), P a) : all A P := h;
-- elimination rule -- elimination rule
all_elim (A : *) (P : A -> *) (h_all : all A P) (a : A) : P a := h_all a; def all_elim (A : *) (P : A -> *) (h_all : all A P) (a : A) : P a := h_all a;
-- -------------------------------------------------------------------------------------------------------------- -- --------------------------------------------------------------------------------------------------------------
-- Equality -- Equality
-- 2nd order Leibniz equality -- 2nd order Leibniz equality
eq (A : *) (x y : A) := forall (P : A -> *), P x -> P y; def eq (A : *) (x y : A) := forall (P : A -> *), P x -> P y;
-- equality is reflexive -- equality is reflexive
eq_refl (A : *) (x : A) : eq A x x := fun (P : A -> *) (Hx : P x) => Hx; def eq_refl (A : *) (x : A) : eq A x x := fun (P : A -> *) (Hx : P x) => Hx;
-- equality is symmetric -- equality is symmetric
eq_sym (A : *) (x y : A) (Hxy : eq A x y) : eq A y x := fun (P : A -> *) (Hy : P y) => def eq_sym (A : *) (x y : A) (Hxy : eq A x y) : eq A y x := fun (P : A -> *) (Hy : P y) =>
Hxy (fun (z : A) => P z -> P x) (fun (Hx : P x) => Hx) Hy; Hxy (fun (z : A) => P z -> P x) (fun (Hx : P x) => Hx) Hy;
-- equality is transitive -- equality is transitive
eq_trans (A : *) (x y z : A) (Hxy : eq A x y) (Hyz : eq A y z) : eq A x z := fun (P : A -> *) (Hx : P x) => def eq_trans (A : *) (x y z : A) (Hxy : eq A x y) (Hyz : eq A y z) : eq A x z := fun (P : A -> *) (Hx : P x) =>
Hyz P (Hxy P Hx); Hyz P (Hxy P Hx);
-- equality is a universal congruence -- equality is a universal congruence
eq_cong (A B : *) (x y : A) (f : A -> B) (H : eq A x y) : eq B (f x) (f y) := def eq_cong (A B : *) (x y : A) (f : A -> B) (H : eq A x y) : eq B (f x) (f y) :=
fun (P : B -> *) (Hfx : P (f x)) => fun (P : B -> *) (Hfx : P (f x)) =>
H (fun (a : A) => P (f a)) Hfx; H (fun (a : A) => P (f a)) Hfx;
@ -116,20 +116,20 @@ eq_cong (A B : *) (x y : A) (f : A -> B) (H : eq A x y) : eq B (f x) (f y) :=
-- Some logic theorems -- Some logic theorems
-- ~(A B) => ~A ∧ ~B -- ~(A B) => ~A ∧ ~B
de_morgan1 (A B : *) (h : not (or A B)) : and (not A) (not B) := def de_morgan1 (A B : *) (h : not (or A B)) : and (not A) (not B) :=
and_intro (not A) (not B) and_intro (not A) (not B)
(fun (a : A) => h (or_intro_l A B a)) (fun (a : A) => h (or_intro_l A B a))
(fun (b : B) => h (or_intro_r A B b)); (fun (b : B) => h (or_intro_r A B b));
-- ~A ∧ ~B => ~(A B) -- ~A ∧ ~B => ~(A B)
de_morgan2 (A B : *) (h : and (not A) (not B)) : not (or A B) := def de_morgan2 (A B : *) (h : and (not A) (not B)) : not (or A B) :=
fun (contra : or A B) => fun (contra : or A B) =>
or_elim A B false contra or_elim A B false contra
(and_elim_l (not A) (not B) h) (and_elim_l (not A) (not B) h)
(and_elim_r (not A) (not B) h); (and_elim_r (not A) (not B) h);
-- ~A ~B => ~(A ∧ B) -- ~A ~B => ~(A ∧ B)
de_morgan3 (A B : *) (h : or (not A) (not B)) : not (and A B) := def de_morgan3 (A B : *) (h : or (not A) (not B)) : not (and A B) :=
fun (contra : and A B) => fun (contra : and A B) =>
or_elim (not A) (not B) false h or_elim (not A) (not B) false h
(fun (na : not A) => na (and_elim_l A B contra)) (fun (na : not A) => na (and_elim_l A B contra))
@ -138,19 +138,19 @@ de_morgan3 (A B : *) (h : or (not A) (not B)) : not (and A B) :=
-- the last one (~(A ∧ B) => ~A ~B) is not possible constructively -- the last one (~(A ∧ B) => ~A ~B) is not possible constructively
-- A ∧ B => B ∧ A -- A ∧ B => B ∧ A
and_comm (A B : *) (h : and A B) : and B A := def and_comm (A B : *) (h : and A B) : and B A :=
and_intro B A and_intro B A
(and_elim_r A B h) (and_elim_r A B h)
(and_elim_l A B h); (and_elim_l A B h);
-- A B => B A -- A B => B A
or_comm (A B : *) (h : or A B) : or B A := def or_comm (A B : *) (h : or A B) : or B A :=
or_elim A B (or B A) h or_elim A B (or B A) h
(fun (a : A) => or_intro_r B A a) (fun (a : A) => or_intro_r B A a)
(fun (b : B) => or_intro_l B A b); (fun (b : B) => or_intro_l B A b);
-- A ∧ (B ∧ C) => (A ∧ B) ∧ C -- A ∧ (B ∧ C) => (A ∧ B) ∧ C
and_assoc_l (A B C : *) (h : and A (and B C)) : and (and A B) C := def and_assoc_l (A B C : *) (h : and A (and B C)) : and (and A B) C :=
let (a := (and_elim_l A (and B C) h)) let (a := (and_elim_l A (and B C) h))
(bc := (and_elim_r A (and B C) h)) (bc := (and_elim_r A (and B C) h))
(b := (and_elim_l B C bc)) (b := (and_elim_l B C bc))
@ -160,7 +160,7 @@ and_assoc_l (A B C : *) (h : and A (and B C)) : and (and A B) C :=
end; end;
-- (A ∧ B) ∧ C => A ∧ (B ∧ C) -- (A ∧ B) ∧ C => A ∧ (B ∧ C)
and_assoc_r (A B C : *) (h : and (and A B) C) : and A (and B C) := def and_assoc_r (A B C : *) (h : and (and A B) C) : and A (and B C) :=
let (ab := and_elim_l (and A B) C h) let (ab := and_elim_l (and A B) C h)
(a := and_elim_l A B ab) (a := and_elim_l A B ab)
(b := and_elim_r A B ab) (b := and_elim_r A B ab)
@ -170,7 +170,7 @@ and_assoc_r (A B C : *) (h : and (and A B) C) : and A (and B C) :=
end; end;
-- A (B C) => (A B) C -- A (B C) => (A B) C
or_assoc_l (A B C : *) (h : or A (or B C)) : or (or A B) C := def or_assoc_l (A B C : *) (h : or A (or B C)) : or (or A B) C :=
or_elim A (or B C) (or (or A B) C) h or_elim A (or B C) (or (or A B) C) h
(fun (a : A) => or_intro_l (or A B) C (or_intro_l A B a)) (fun (a : A) => or_intro_l (or A B) C (or_intro_l A B a))
(fun (g : or B C) => (fun (g : or B C) =>
@ -179,7 +179,7 @@ or_assoc_l (A B C : *) (h : or A (or B C)) : or (or A B) C :=
(fun (c : C) => or_intro_r (or A B) C c)); (fun (c : C) => or_intro_r (or A B) C c));
-- (A B) C => A (B C) -- (A B) C => A (B C)
or_assoc_r (A B C : *) (h : or (or A B) C) : or A (or B C) := def or_assoc_r (A B C : *) (h : or (or A B) C) : or A (or B C) :=
or_elim (or A B) C (or A (or B C)) h or_elim (or A B) C (or A (or B C)) h
(fun (g : or A B) => (fun (g : or A B) =>
or_elim A B (or A (or B C)) g or_elim A B (or A (or B C)) g
@ -188,7 +188,7 @@ or_assoc_r (A B C : *) (h : or (or A B) C) : or A (or B C) :=
(fun (c : C) => or_intro_r A (or B C) (or_intro_r B C c)); (fun (c : C) => or_intro_r A (or B C) (or_intro_r B C c));
-- A ∧ (B C) => A ∧ B A ∧ C -- A ∧ (B C) => A ∧ B A ∧ C
and_distrib_l_or (A B C : *) (h : and A (or B C)) : or (and A B) (and A C) := def and_distrib_l_or (A B C : *) (h : and A (or B C)) : or (and A B) (and A C) :=
or_elim B C (or (and A B) (and A C)) (and_elim_r A (or B C) h) or_elim B C (or (and A B) (and A C)) (and_elim_r A (or B C) h)
(fun (b : B) => or_intro_l (and A B) (and A C) (fun (b : B) => or_intro_l (and A B) (and A C)
(and_intro A B (and_elim_l A (or B C) h) b)) (and_intro A B (and_elim_l A (or B C) h) b))
@ -196,7 +196,7 @@ and_distrib_l_or (A B C : *) (h : and A (or B C)) : or (and A B) (and A C) :=
(and_intro A C (and_elim_l A (or B C) h) c)); (and_intro A C (and_elim_l A (or B C) h) c));
-- A ∧ B A ∧ C => A ∧ (B C) -- A ∧ B A ∧ C => A ∧ (B C)
and_factor_l_or (A B C : *) (h : or (and A B) (and A C)) : and A (or B C) := def and_factor_l_or (A B C : *) (h : or (and A B) (and A C)) : and A (or B C) :=
or_elim (and A B) (and A C) (and A (or B C)) h or_elim (and A B) (and A C) (and A (or B C)) h
(fun (ab : and A B) => and_intro A (or B C) (fun (ab : and A B) => and_intro A (or B C)
(and_elim_l A B ab) (and_elim_l A B ab)
@ -207,9 +207,9 @@ and_factor_l_or (A B C : *) (h : or (and A B) (and A C)) : and A (or B C) :=
-- Thanks Quinn! -- Thanks Quinn!
-- A B => ~B => A -- A B => ~B => A
disj_syllog (A B : *) (nb : not B) (hor : or A B) : A := def disj_syllog (A B : *) (nb : not B) (hor : or A B) : A :=
or_elim A B A hor (fun (a : A) => a) (fun (b : B) => nb b A); or_elim A B A hor (fun (a : A) => a) (fun (b : B) => nb b A);
-- (A => B) => ~B => ~A -- (A => B) => ~B => ~A
contrapositive (A B : *) (f : A -> B) (nb : not B) : not A := def contrapositive (A B : *) (f : A -> B) (nb : not B) : not A :=
fun (a : A) => nb (f a); fun (a : A) => nb (f a);

View file

@ -5,7 +5,7 @@
@include logic.pg @include logic.pg
@include algebra.pg @include algebra.pg
comp (A B C : *) (g : B -> C) (f : A -> B) (x : A) : C := def comp (A B C : *) (g : B -> C) (f : A -> B) (x : A) : C :=
g (f x); g (f x);
-- }}} -- }}}
@ -24,7 +24,7 @@ comp (A B C : *) (g : B -> C) (f : A -> B) (x : A) : C :=
-- defined is a value of the asserted type. For example, we will use the axiom -- defined is a value of the asserted type. For example, we will use the axiom
-- system to assert the existence of a type of natural numbers -- system to assert the existence of a type of natural numbers
nat : * := axiom; axiom nat : *;
-- As you can imagine, this can be risky. For instance, there's nothing stopping -- As you can imagine, this can be risky. For instance, there's nothing stopping
-- us from saying -- us from saying
@ -47,21 +47,21 @@ nat : * := axiom;
-- (https://en.wikipedia.org/wiki/Peano_axioms). -- (https://en.wikipedia.org/wiki/Peano_axioms).
-- axiom 1: 0 is a natural number -- axiom 1: 0 is a natural number
zero : nat := axiom; axiom zero : nat;
-- axiom 6: For every n, S n is a natural number. -- axiom 6: For every n, S n is a natural number.
suc (n : nat) : nat := axiom; axiom suc (n : nat) : nat;
-- axiom 7: If S n = S m, then n = m. -- axiom 7: If S n = S m, then n = m.
suc_inj : forall (n m : nat), eq nat (suc n) (suc m) -> eq nat n m := axiom; axiom suc_inj : forall (n m : nat), eq nat (suc n) (suc m) -> eq nat n m;
-- axiom 8: No successor of any natural number is zero. -- axiom 8: No successor of any natural number is zero.
suc_nonzero : forall (n : nat), not (eq nat (suc n) zero) := axiom; axiom suc_nonzero : forall (n : nat), not (eq nat (suc n) zero);
-- axiom 9: Induction! For any proposition P on natural numbers, if P(0) holds, -- axiom 9: Induction! For any proposition P on natural numbers, if P(0) holds,
-- and if for every natural number n, P(n) ⇒ P(S n), then P holds for all n. -- and if for every natural number n, P(n) ⇒ P(S n), then P holds for all n.
nat_ind : forall (P : nat -> *), P zero -> (forall (n : nat), P n -> P (suc n)) axiom nat_ind : forall (P : nat -> *), P zero -> (forall (n : nat), P n -> P (suc n))
-> forall (n : nat), P n := axiom; -> forall (n : nat), P n;
-- }}} -- }}}
@ -77,27 +77,27 @@ nat_ind : forall (P : nat -> *), P zero -> (forall (n : nat), P n -> P (suc n))
-- long and complicated really quickly. -- long and complicated really quickly.
-- Some abbreviations for common numbers. -- Some abbreviations for common numbers.
one : nat := suc zero; def one : nat := suc zero;
two : nat := suc one; def two : nat := suc one;
three : nat := suc two; def three : nat := suc two;
four : nat := suc three; def four : nat := suc three;
five : nat := suc four; def five : nat := suc four;
-- First, the predecessor of n is m if n = suc m. -- First, the predecessor of n is m if n = suc m.
pred (n m : nat) : * := eq nat n (suc m); def pred (n m : nat) : * := eq nat n (suc m);
-- Our claim is a disjunction, whose first option is that n = 0. -- Our claim is a disjunction, whose first option is that n = 0.
szc_l (n : nat) := eq nat n zero; def szc_l (n : nat) := eq nat n zero;
-- The second option is that n has a predecessor. -- The second option is that n has a predecessor.
szc_r (n : nat) := exists nat (pred n); def szc_r (n : nat) := exists nat (pred n);
-- So the claim we are trying to prove is that either one of the above options -- So the claim we are trying to prove is that either one of the above options
-- holds for every n. -- holds for every n.
szc (n : nat) := or (szc_l n) (szc_r n); def szc (n : nat) := or (szc_l n) (szc_r n);
-- And here's our proof! -- And here's our proof!
suc_or_zero : forall (n : nat), szc n := def suc_or_zero : forall (n : nat), szc n :=
-- We will prove this by induction. -- We will prove this by induction.
nat_ind szc nat_ind szc
-- For the base case, the first option holds, i.e. 0 = 0 -- For the base case, the first option holds, i.e. 0 = 0
@ -157,30 +157,30 @@ suc_or_zero : forall (n : nat), szc n :=
-- {{{ Defining R -- {{{ Defining R
-- Here is condition 1 formally expressed in perga. -- Here is condition 1 formally expressed in perga.
cond1 (A : *) (z : A) (fS : nat -> A -> A) (Q : nat -> A -> *) := def cond1 (A : *) (z : A) (fS : nat -> A -> A) (Q : nat -> A -> *) :=
Q zero z; Q zero z;
-- Likewise for condition 2. -- Likewise for condition 2.
cond2 (A : *) (z : A) (fS : nat -> A -> A) (Q : nat -> A -> *) := def cond2 (A : *) (z : A) (fS : nat -> A -> A) (Q : nat -> A -> *) :=
forall (n : nat) (y : A), Q n y -> Q (suc n) (fS n y); forall (n : nat) (y : A), Q n y -> Q (suc n) (fS n y);
-- From here we can define R. -- From here we can define R.
rec_rel (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) := def rec_rel (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) :=
forall (Q : nat -> A -> *), cond1 A z fS Q -> cond2 A z fS Q -> Q x y; forall (Q : nat -> A -> *), cond1 A z fS Q -> cond2 A z fS Q -> Q x y;
-- }}} -- }}}
-- {{{ R is total -- {{{ R is total
total (A B : *) (R : A -> B -> *) := forall (a : A), exists B (R a); def total (A B : *) (R : A -> B -> *) := forall (a : A), exists B (R a);
rec_rel_cond1 (A : *) (z : A) (fS : nat -> A -> A) : cond1 A z fS (rec_rel A z fS) := def rec_rel_cond1 (A : *) (z : A) (fS : nat -> A -> A) : cond1 A z fS (rec_rel A z fS) :=
fun (Q : nat -> A -> *) (h1 : cond1 A z fS Q) (h2 : cond2 A z fS Q) => h1; fun (Q : nat -> A -> *) (h1 : cond1 A z fS Q) (h2 : cond2 A z fS Q) => h1;
rec_rel_cond2 (A : *) (z : A) (fS : nat -> A -> A) : cond2 A z fS (rec_rel A z fS) := def rec_rel_cond2 (A : *) (z : A) (fS : nat -> A -> A) : cond2 A z fS (rec_rel A z fS) :=
fun (n : nat) (y : A) (h : rec_rel A z fS n y) fun (n : nat) (y : A) (h : rec_rel A z fS n y)
(Q : nat -> A -> *) (h1 : cond1 A z fS Q) (h2 : cond2 A z fS Q) => (Q : nat -> A -> *) (h1 : cond1 A z fS Q) (h2 : cond2 A z fS Q) =>
h2 n y (h Q h1 h2); h2 n y (h Q h1 h2);
rec_rel_total (A : *) (z : A) (fS : nat -> A -> A) : total nat A (rec_rel A z fS) := def rec_rel_total (A : *) (z : A) (fS : nat -> A -> A) : total nat A (rec_rel A z fS) :=
let (R := rec_rel A z fS) let (R := rec_rel A z fS)
(P (x : nat) := exists A (R x)) (P (x : nat) := exists A (R x))
(c1 := cond1 A z fS) (c1 := cond1 A z fS)
@ -196,27 +196,27 @@ rec_rel_total (A : *) (z : A) (fS : nat -> A -> A) : total nat A (rec_rel A z fS
-- }}} -- }}}
-- {{{ Defining R2 -- {{{ Defining R2
alt_cond1 (A : *) (z : A) (x : nat) (y : A) := def alt_cond1 (A : *) (z : A) (x : nat) (y : A) :=
and (eq nat x zero) (eq A y z); and (eq nat x zero) (eq A y z);
cond_y2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) def cond_y2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A)
(x2 : nat) (y2 : A) := (x2 : nat) (y2 : A) :=
and (eq A y (fS x2 y2)) (rec_rel A z fS x2 y2); and (eq A y (fS x2 y2)) (rec_rel A z fS x2 y2);
cond_x2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) (x2 : nat) := def cond_x2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) (x2 : nat) :=
and (pred x x2) (exists A (cond_y2 A z fS x y x2)); and (pred x x2) (exists A (cond_y2 A z fS x y x2));
alt_cond2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) := def alt_cond2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) :=
exists nat (cond_x2 A z fS x y); exists nat (cond_x2 A z fS x y);
rec_rel_alt (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) := def rec_rel_alt (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) :=
or (alt_cond1 A z x y) (alt_cond2 A z fS x y); or (alt_cond1 A z x y) (alt_cond2 A z fS x y);
-- }}} -- }}}
-- {{{ R = R2 -- {{{ R = R2
-- {{{ R2 ⊆ R -- {{{ R2 ⊆ R
R2_sub_R (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) : def R2_sub_R (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) :
rec_rel_alt A z fS x y -> rec_rel A z fS x y := rec_rel_alt A z fS x y -> rec_rel A z fS x y :=
let (R := rec_rel A z fS) let (R := rec_rel A z fS)
(R2 := rec_rel_alt A z fS) (R2 := rec_rel_alt A z fS)
@ -257,13 +257,13 @@ R2_sub_R (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) :
-- }}} -- }}}
-- {{{ R ⊆ R2 -- {{{ R ⊆ R2
R2_cond1 (A : *) (z : A) (fS : nat -> A -> A) : cond1 A z fS (rec_rel_alt A z fS) := def R2_cond1 (A : *) (z : A) (fS : nat -> A -> A) : cond1 A z fS (rec_rel_alt A z fS) :=
or_intro_l (alt_cond1 A z zero z) (alt_cond2 A z fS zero z) or_intro_l (alt_cond1 A z zero z) (alt_cond2 A z fS zero z)
(and_intro (eq nat zero zero) (eq A z z) (and_intro (eq nat zero zero) (eq A z z)
(eq_refl nat zero) (eq_refl nat zero)
(eq_refl A z)); (eq_refl A z));
R2_cond2 (A : *) (z : A) (fS : nat -> A -> A) : cond2 A z fS (rec_rel_alt A z fS) := def R2_cond2 (A : *) (z : A) (fS : nat -> A -> A) : cond2 A z fS (rec_rel_alt A z fS) :=
let (R := rec_rel A z fS) let (R := rec_rel A z fS)
(R2 := rec_rel_alt A z fS) (R2 := rec_rel_alt A z fS)
in in
@ -284,7 +284,7 @@ R2_cond2 (A : *) (z : A) (fS : nat -> A -> A) : cond2 A z fS (rec_rel_alt A z fS
end end
end; end;
R_sub_R2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) : def R_sub_R2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) :
rec_rel A z fS x y -> rec_rel_alt A z fS x y := rec_rel A z fS x y -> rec_rel_alt A z fS x y :=
let (R := rec_rel A z fS) let (R := rec_rel A z fS)
(R2 := rec_rel_alt A z fS) (R2 := rec_rel_alt A z fS)
@ -298,12 +298,12 @@ R_sub_R2 (A : *) (z : A) (fS : nat -> A -> A) (x : nat) (y : A) :
-- {{{ R2 (hence R) is functional -- {{{ R2 (hence R) is functional
fl_in (A B : *) (R : A -> B -> *) (x : A) := forall (y1 y2 : B), def fl_in (A B : *) (R : A -> B -> *) (x : A) := forall (y1 y2 : B),
R x y1 -> R x y2 -> eq B y1 y2; R x y1 -> R x y2 -> eq B y1 y2;
fl (A B : *) (R : A -> B -> *) := forall (x : A), fl_in A B R x; def fl (A B : *) (R : A -> B -> *) := forall (x : A), fl_in A B R x;
R2_zero (A : *) (z : A) (fS : nat -> A -> A) (y : A) : def R2_zero (A : *) (z : A) (fS : nat -> A -> A) (y : A) :
rec_rel_alt A z fS zero y -> eq A y z := rec_rel_alt A z fS zero y -> eq A y z :=
let (R2 := rec_rel_alt A z fS) let (R2 := rec_rel_alt A z fS)
(ac1 := alt_cond1 A z zero y) (ac1 := alt_cond1 A z zero y)
@ -321,7 +321,7 @@ R2_zero (A : *) (z : A) (fS : nat -> A -> A) (y : A) :
(eq A y z))) (eq A y z)))
end; end;
R2_suc (A : *) (z : A) (fS : nat -> A -> A) (x2 : nat) (y : A) : def R2_suc (A : *) (z : A) (fS : nat -> A -> A) (x2 : nat) (y : A) :
rec_rel_alt A z fS (suc x2) y -> exists A (cond_y2 A z fS (suc x2) y x2) := rec_rel_alt A z fS (suc x2) y -> exists A (cond_y2 A z fS (suc x2) y x2) :=
let (R2 := rec_rel_alt A z fS) let (R2 := rec_rel_alt A z fS)
(x := suc x2) (x := suc x2)
@ -347,7 +347,7 @@ R2_suc (A : *) (z : A) (fS : nat -> A -> A) (x2 : nat) (y : A) :
end)) end))
end; end;
R2_functional (A : *) (z : A) (fS : nat -> A -> A) : fl nat A (rec_rel_alt A z fS) := def R2_functional (A : *) (z : A) (fS : nat -> A -> A) : fl nat A (rec_rel_alt A z fS) :=
let (R := rec_rel A z fS) let (R := rec_rel A z fS)
(R2 := rec_rel_alt A z fS) (R2 := rec_rel_alt A z fS)
(f_in := fl_in nat A R2) (f_in := fl_in nat A R2)
@ -385,7 +385,7 @@ R2_functional (A : *) (z : A) (fS : nat -> A -> A) : fl nat A (rec_rel_alt A z f
-- }}} -- }}}
rec_def (A : *) (z : A) (fS : nat -> A -> A) (x : nat) : A := def rec_def (A : *) (z : A) (fS : nat -> A -> A) (x : nat) : A :=
exists_elim A A (rec_rel A z fS x) (rec_rel_total A z fS x) exists_elim A A (rec_rel A z fS x) (rec_rel_total A z fS x)
(fun (y : A) (_ : rec_rel A z fS x y) => y); (fun (y : A) (_ : rec_rel A z fS x y) => y);

View file

@ -49,7 +49,12 @@ findType g (Pi _ a b) = do
i <- findLevel g a i <- findLevel g a
j <- findLevel (incIndices a : map incIndices g) b j <- findLevel (incIndices a : map incIndices g) b
pure $ Level $ max (i - 1) j -- This feels very sketchy, but certainly adds impredicativity pure $ Level $ max (i - 1) j -- This feels very sketchy, but certainly adds impredicativity
findType g (Let _ v b) = findType g (subst 0 v b) findType g (Let _ Nothing v b) = findType g (subst 0 v b)
findType g e@(Let _ (Just t) v b) = do
res <- findType g (subst 0 v b)
equiv <- betaEquiv t res
unless equiv $ throwError $ NotEquivalent t res e
pure t
checkType :: Env -> Expr -> Result Expr checkType :: Env -> Expr -> Result Expr
checkType env t = runReaderT (findType [] t) env checkType env t = runReaderT (findType [] t) env

43
lib/Elaborator.hs Normal file
View file

@ -0,0 +1,43 @@
module Elaborator where
import Data.List (elemIndex)
import Expr (Expr)
import qualified Expr as E
import IR (IRExpr)
import qualified IR as I
type Binders = [Text]
saveBinders :: State Binders a -> State Binders a
saveBinders action = do
binders <- get
res <- action
put binders
pure res
elaborate :: IRExpr -> Expr
elaborate ir = evalState (elaborate' ir) []
where
elaborate' :: IRExpr -> State Binders Expr
elaborate' (I.Var n) = do
binders <- get
pure $ E.Var n . fromIntegral <$> elemIndex n binders ?: E.Free n
elaborate' (I.Level level) = pure $ E.Level level
elaborate' (I.App m n) = E.App <$> elaborate' m <*> elaborate' n
elaborate' (I.Abs x t b) = saveBinders $ do
t' <- elaborate' t
modify (x :)
E.Abs x t' <$> elaborate' b
elaborate' (I.Pi x t b) = saveBinders $ do
t' <- elaborate' t
modify (x :)
E.Pi x t' <$> elaborate' b
elaborate' (I.Let name Nothing val body) = saveBinders $ do
val' <- elaborate' val
modify (name :)
E.Let name Nothing val' <$> elaborate' body
elaborate' (I.Let name (Just ty) val body) = saveBinders $ do
val' <- elaborate' val
ty' <- elaborate' ty
modify (name :)
E.Let name (Just ty') val' <$> elaborate' body

View file

@ -10,9 +10,6 @@ import Relude.Extra.Lens
data Definition = Def {_ty :: Expr, _val :: Expr} data Definition = Def {_ty :: Expr, _val :: Expr}
makeDef :: Expr -> Expr -> Definition
makeDef typ value = Def{_ty = typ, _val = value}
tyL :: Lens' Definition Expr tyL :: Lens' Definition Expr
tyL = lens _ty setter tyL = lens _ty setter
where where
@ -46,7 +43,7 @@ subst _ _ (Level i) = Level i
subst k s (App m n) = App (subst k s m) (subst k s n) subst k s (App m n) = App (subst k s m) (subst k s n)
subst k s (Abs x m n) = Abs x (subst k s m) (subst (k + 1) (incIndices s) n) subst k s (Abs x m n) = Abs x (subst k s m) (subst (k + 1) (incIndices s) n)
subst k s (Pi x m n) = Pi x (subst k s m) (subst (k + 1) (incIndices s) n) subst k s (Pi x m n) = Pi x (subst k s m) (subst (k + 1) (incIndices s) n)
subst k s (Let x v b) = Let x (subst k s v) (subst (k + 1) (incIndices s) b) subst k s (Let x t v b) = Let x t (subst k s v) (subst (k + 1) (incIndices s) b)
envLookupVal :: Text -> ReaderT Env Result Expr envLookupVal :: Text -> ReaderT Env Result Expr
envLookupVal n = asks ((_val <$>) . M.lookup n) >>= maybe (throwError $ UnboundVariable n) pure envLookupVal n = asks ((_val <$>) . M.lookup n) >>= maybe (throwError $ UnboundVariable n) pure
@ -63,7 +60,7 @@ whnf (App m n) = do
then pure $ App m n then pure $ App m n
else whnf $ App m' n else whnf $ App m' n
whnf (Free n) = envLookupVal n >>= whnf whnf (Free n) = envLookupVal n >>= whnf
whnf (Let _ v b) = whnf $ subst 0 v b whnf (Let _ _ v b) = whnf $ subst 0 v b
whnf e = pure e whnf e = pure e
reduce :: Expr -> ReaderT Env Result Expr reduce :: Expr -> ReaderT Env Result Expr
@ -72,7 +69,7 @@ reduce (App m n) = App <$> reduce m <*> reduce n
reduce (Abs x t v) = Abs x <$> reduce t <*> reduce v reduce (Abs x t v) = Abs x <$> reduce t <*> reduce v
reduce (Pi x t v) = Pi x <$> reduce t <*> reduce v reduce (Pi x t v) = Pi x <$> reduce t <*> reduce v
reduce (Free n) = envLookupVal n reduce (Free n) = envLookupVal n
reduce (Let _ v b) = pure $ subst 0 v b reduce (Let _ _ v b) = pure $ subst 0 v b
reduce e = pure e reduce e = pure e
normalize :: Expr -> ReaderT Env Result Expr normalize :: Expr -> ReaderT Env Result Expr
@ -91,19 +88,22 @@ betaEquiv e1 e2
case (e1', e2') of case (e1', e2') of
(Var k1 _, Var k2 _) -> pure $ k1 == k2 (Var k1 _, Var k2 _) -> pure $ k1 == k2
(Free n, Free m) -> pure $ n == m (Free n, Free m) -> pure $ n == m
(Axiom n, Axiom m) -> pure $ n == m
(Free n, e) -> envLookupVal n >>= betaEquiv e (Free n, e) -> envLookupVal n >>= betaEquiv e
(e, Free n) -> envLookupVal n >>= betaEquiv e (e, Free n) -> envLookupVal n >>= betaEquiv e
(Axiom n, Axiom m) -> pure $ n == m
(Level i, Level j) -> pure $ i == j (Level i, Level j) -> pure $ i == j
(Abs _ t1 v1, Abs _ t2 v2) -> (&&) <$> betaEquiv t1 t2 <*> betaEquiv v1 v2 -- i want idiom brackets (Abs _ t1 v1, Abs _ t2 v2) -> (&&) <$> betaEquiv t1 t2 <*> betaEquiv v1 v2 -- i want idiom brackets
(Pi _ t1 v1, Pi _ t2 v2) -> (&&) <$> betaEquiv t1 t2 <*> betaEquiv v1 v2 (Pi _ t1 v1, Pi _ t2 v2) -> (&&) <$> betaEquiv t1 t2 <*> betaEquiv v1 v2
(App m1 n1, App m2 n2) -> (&&) <$> betaEquiv m1 m2 <*> betaEquiv n1 n2 (App m1 n1, App m2 n2) -> (&&) <$> betaEquiv m1 m2 <*> betaEquiv n1 n2
(Let _ v b, e) -> betaEquiv (subst 0 v b) e (Let _ _ v b, e) -> betaEquiv (subst 0 v b) e
(e, Let _ v b) -> betaEquiv (subst 0 v b) e (e, Let _ _ v b) -> betaEquiv (subst 0 v b) e
_ -> pure False -- remaining cases impossible or false _ -> pure False -- remaining cases impossible or false
checkBeta :: Env -> Expr -> Expr -> Result Bool checkBeta :: Env -> Expr -> Expr -> Expr -> Result ()
checkBeta env e1 e2 = runReaderT (betaEquiv e1 e2) env checkBeta env e1 e2 ctxt = case runReaderT (betaEquiv e1 e2) env of
Left err -> Left err
Right False -> Left $ NotEquivalent e1 e2 ctxt
Right True -> Right ()
isSortPure :: Expr -> Bool isSortPure :: Expr -> Bool
isSortPure (Level _) = True isSortPure (Level _) = True

View file

@ -8,18 +8,18 @@ data Expr where
App :: Expr -> Expr -> Expr App :: Expr -> Expr -> Expr
Abs :: Text -> Expr -> Expr -> Expr Abs :: Text -> Expr -> Expr -> Expr
Pi :: Text -> Expr -> Expr -> Expr Pi :: Text -> Expr -> Expr -> Expr
Let :: Text -> Expr -> Expr -> Expr Let :: Text -> Maybe Expr -> Expr -> Expr -> Expr
deriving (Show, Ord) deriving (Show, Ord)
instance Eq Expr where instance Eq Expr where
(Var _ n) == (Var _ m) = n == m (Var _ n) == (Var _ m) = n == m
(Free s) == (Free t) = s == t (Free s) == (Free t) = s == t
(Axiom a) == (Axiom b) = a == b (Axiom s) == (Axiom t) = s == t
(Level i) == (Level j) = i == j (Level i) == (Level j) = i == j
(App e1 e2) == (App f1 f2) = e1 == f1 && e2 == f2 (App e1 e2) == (App f1 f2) = e1 == f1 && e2 == f2
(Abs _ t1 b1) == (Abs _ t2 b2) = t1 == t2 && b1 == b2 (Abs _ t1 b1) == (Abs _ t2 b2) = t1 == t2 && b1 == b2
(Pi _ t1 b1) == (Pi _ t2 b2) = t1 == t2 && b1 == b2 (Pi _ t1 b1) == (Pi _ t2 b2) = t1 == t2 && b1 == b2
(Let _ v1 b1) == (Let _ v2 b2) = v1 == v2 && b1 == b2 (Let _ _ v1 b1) == (Let _ _ v2 b2) = v1 == v2 && b1 == b2
_ == _ = False _ == _ = False
occursFree :: Integer -> Expr -> Bool occursFree :: Integer -> Expr -> Bool
@ -30,7 +30,7 @@ occursFree _ (Level _) = False
occursFree n (App a b) = on (||) (occursFree n) a b occursFree n (App a b) = on (||) (occursFree n) a b
occursFree n (Abs _ a b) = occursFree n a || occursFree (n + 1) b occursFree n (Abs _ a b) = occursFree n a || occursFree (n + 1) b
occursFree n (Pi _ a b) = occursFree n a || occursFree (n + 1) b occursFree n (Pi _ a b) = occursFree n a || occursFree (n + 1) b
occursFree n (Let _ v b) = occursFree n v || occursFree (n + 1) b occursFree n (Let _ _ v b) = occursFree n v || occursFree (n + 1) b
shiftIndices :: Integer -> Integer -> Expr -> Expr shiftIndices :: Integer -> Integer -> Expr -> Expr
shiftIndices d c (Var x k) shiftIndices d c (Var x k)
@ -42,7 +42,7 @@ shiftIndices _ _ (Level i) = Level i
shiftIndices d c (App m n) = App (shiftIndices d c m) (shiftIndices d c n) shiftIndices d c (App m n) = App (shiftIndices d c m) (shiftIndices d c n)
shiftIndices d c (Abs x m n) = Abs x (shiftIndices d c m) (shiftIndices d (c + 1) n) shiftIndices d c (Abs x m n) = Abs x (shiftIndices d c m) (shiftIndices d (c + 1) n)
shiftIndices d c (Pi x m n) = Pi x (shiftIndices d c m) (shiftIndices d (c + 1) n) shiftIndices d c (Pi x m n) = Pi x (shiftIndices d c m) (shiftIndices d (c + 1) n)
shiftIndices d c (Let x v b) = Let x (shiftIndices d c v) (shiftIndices d (c + 1) b) shiftIndices d c (Let x t v b) = Let x t (shiftIndices d c v) (shiftIndices d (c + 1) b)
incIndices :: Expr -> Expr incIndices :: Expr -> Expr
incIndices = shiftIndices 1 0 incIndices = shiftIndices 1 0
@ -63,7 +63,7 @@ collectLambdas (Abs x ty body) = ((x, ty) : params, final)
collectLambdas e = ([], e) collectLambdas e = ([], e)
collectLets :: Expr -> ([Binding], Expr) collectLets :: Expr -> ([Binding], Expr)
collectLets (Let x val body) = ((x, params', val') : bindings, final) collectLets (Let x _ val body) = ((x, params', val') : bindings, final)
where where
(bindings, final) = collectLets body (bindings, final) = collectLets body
(params, val') = collectLambdas val (params, val') = collectLambdas val

41
lib/IR.hs Normal file
View file

@ -0,0 +1,41 @@
module IR where
type Param = (Text, IRExpr)
data IRExpr
= Var {varName :: Text}
| Level {level :: Integer}
| App
{ appFunc :: IRExpr
, appArg :: IRExpr
}
| Abs
{ absParamName :: Text
, absParamType :: IRExpr
, absBody :: IRExpr
}
| Pi
{ piParamName :: Text
, piParamType :: IRExpr
, piBody :: IRExpr
}
| Let
{ letVarName :: Text
, letAscription :: Maybe IRExpr
, letValue :: IRExpr
, letBody :: IRExpr
}
deriving (Show, Eq, Ord)
data IRDef
= Def
{ defName :: Text
, defAscription :: Maybe IRExpr
, defBody :: IRExpr
}
| Axiom
{ axiomName :: Text
, axiomAscription :: IRExpr
}
type IRProgram = [IRDef]

View file

@ -1,37 +1,21 @@
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE TupleSections #-}
module Parser (parseDef, parseDefEmpty, parseExpr, parseProgram, handleFile) where module Parser where
import Check
import Control.Monad.Except import Control.Monad.Except
import Data.List (elemIndex, foldl, foldl1) import Data.List (foldl1)
import qualified Data.Map.Strict as M
import qualified Data.Text as T import qualified Data.Text as T
import Errors (Error (..)) import Errors (Error (..))
import Eval import IR
import Expr (Expr (..), incIndices)
import Preprocessor import Preprocessor
import Relude.Extra.Lens import Text.Megaparsec (Parsec, ShowErrorComponent (..), between, choice, chunk, errorBundlePretty, label, runParser, try)
import Text.Megaparsec (ParsecT, ShowErrorComponent (..), between, choice, chunk, customFailure, errorBundlePretty, label, runParserT, try)
import Text.Megaparsec.Char import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L import qualified Text.Megaparsec.Char.Lexer as L
data InnerState = IS {_binders :: [Text], _env :: Env}
bindsL :: Lens' InnerState [Text]
bindsL = lens _binders setter
where
setter (IS{_env}) new = IS{_env, _binders = new}
envL :: Lens' InnerState Env
envL = lens _env setter
where
setter (IS{_binders}) new = IS{_env = new, _binders}
newtype TypeError = TE Error newtype TypeError = TE Error
deriving (Eq, Ord) deriving (Eq, Ord)
type Parser = ParsecT TypeError Text (State InnerState) type Parser = Parsec TypeError Text
instance ShowErrorComponent TypeError where instance ShowErrorComponent TypeError where
showErrorComponent (TE e) = toString e showErrorComponent (TE e) = toString e
@ -50,7 +34,10 @@ eat :: Text -> Parser ()
eat = void . lexeme . chunk eat = void . lexeme . chunk
keywords :: [Text] keywords :: [Text]
keywords = ["forall", "let", "in", "end", "fun"] keywords = ["forall", "let", "in", "end", "fun", "def", "axiom"]
reservedChars :: [Char]
reservedChars = "();:"
pIdentifier :: Parser Text pIdentifier :: Parser Text
pIdentifier = try $ label "identifier" $ lexeme $ do pIdentifier = try $ label "identifier" $ lexeme $ do
@ -62,178 +49,135 @@ pIdentifier = try $ label "identifier" $ lexeme $ do
"Reserved word: " ++ T.unpack ident "Reserved word: " ++ T.unpack ident
pure ident pure ident
pVar :: Parser Expr pVar :: Parser IRExpr
pVar = label "variable" $ lexeme $ do pVar = label "variable" $ lexeme $ Var <$> pIdentifier
name <- pIdentifier
binders <- view bindsL <$> get
pure $ Var name . fromIntegral <$> elemIndex name binders ?: Free name
defChoice :: NonEmpty Text -> Parser () defChoice :: NonEmpty Text -> Parser ()
defChoice options = lexeme $ label (T.unpack $ head options) $ void $ choice $ fmap chunk options defChoice options = lexeme $ label (T.unpack $ head options) $ void $ choice $ fmap chunk options
pParamGroup :: Parser [(Text, Expr)] pParamGroup :: Parser [Param]
pParamGroup = lexeme $ label "parameter group" $ between (char '(') (char ')') $ do pParamGroup = lexeme $ label "parameter group" $ between (char '(') (char ')') $ do
idents <- some pIdentifier idents <- some pIdentifier
eat ":" eat ":"
ty <- pExpr ty <- pIRExpr
modify $ over bindsL $ flip (foldl $ flip (:)) idents pure $ map (,ty) idents
pure $ zip idents (iterate incIndices ty)
pSomeParams :: Parser [(Text, Expr)] pSomeParams :: Parser [Param]
pSomeParams = lexeme $ concat <$> some pParamGroup pSomeParams = lexeme $ concat <$> some pParamGroup
pManyParams :: Parser [(Text, Expr)] pManyParams :: Parser [Param]
pManyParams = lexeme $ concat <$> many pParamGroup pManyParams = lexeme $ concat <$> many pParamGroup
withBinders :: Parser a -> Parser a pLAbs :: Parser IRExpr
withBinders parser = do pLAbs = lexeme $ label "λ-abstraction" $ do
oldBinders <- view bindsL <$> get
result <- parser
modify $ set bindsL oldBinders
pure result
pLAbs :: Parser Expr
pLAbs = lexeme $ label "λ-abstraction" $ withBinders $ do
_ <- defChoice $ "λ" :| ["fun"] _ <- defChoice $ "λ" :| ["fun"]
params <- pSomeParams params <- pSomeParams
_ <- defChoice $ "=>" :| [""] _ <- defChoice $ "=>" :| [""]
body <- pExpr body <- pIRExpr
pure $ foldr (uncurry Abs) body params pure $ foldr (uncurry Abs) body params
pPAbs :: Parser Expr pPAbs :: Parser IRExpr
pPAbs = lexeme $ label "Π-abstraction" $ withBinders $ do pPAbs = lexeme $ label "Π-abstraction" $ do
_ <- defChoice $ "" :| ["forall", ""] _ <- defChoice $ "" :| ["forall", ""]
params <- pSomeParams params <- pSomeParams
eat "," eat ","
body <- pExpr body <- pIRExpr
pure $ foldr (uncurry Pi) body params pure $ foldr (uncurry Pi) body params
pBinding :: Parser (Text, Expr) pBinding :: Parser (Text, Maybe IRExpr, IRExpr)
pBinding = lexeme $ label "binding" $ do pBinding = lexeme $ label "binding" $ do
env <- get
eat "(" eat "("
ident <- pIdentifier ident <- pIdentifier
params <- pManyParams params <- pManyParams
ascription <- optional pAscription
eat ":=" eat ":="
value <- pExpr value <- pIRExpr
eat ")" eat ")"
put env pure
modify $ over bindsL (ident :) ( ident
pure (ident, foldr (uncurry Abs) value params) , flip (foldr (uncurry Pi)) params <$> ascription
, foldr (uncurry Abs) value params
)
pLet :: Parser Expr pLet :: Parser IRExpr
pLet = lexeme $ label "let expression" $ withBinders $ do pLet = lexeme $ label "let expression" $ do
eat "let" eat "let"
bindings <- some pBinding bindings <- some pBinding
eat "in" eat "in"
body <- try pExpr body <- try pIRExpr
eat "end" eat "end"
pure $ foldr (uncurry Let) body bindings pure $ foldr letTuple body bindings
where
letTuple :: (Text, Maybe IRExpr, IRExpr) -> IRExpr -> IRExpr
letTuple (name, ascription, value) = Let name ascription value
pArrow :: Parser Expr pArrow :: Parser IRExpr
pArrow = lexeme $ label "->" $ do pArrow = lexeme $ label "->" $ do
a <- pAppTerm a <- pAppTerm
_ <- defChoice $ "->" :| [""] _ <- defChoice $ "->" :| [""]
Pi "" a . incIndices <$> pExpr Pi "" a <$> pIRExpr
pApp :: Parser Expr pApp :: Parser IRExpr
pApp = lexeme $ foldl1 App <$> some pTerm pApp = lexeme $ foldl1 App <$> some pTerm
pStar :: Parser Expr pStar :: Parser IRExpr
pStar = lexeme $ Level 0 <$ eat "*" pStar = lexeme $ Level 0 <$ eat "*"
pNumSort :: Parser Expr pNumSort :: Parser IRExpr
pNumSort = lexeme $ label "sort" $ eat "*" >> Level <$> L.decimal pNumSort = lexeme $ label "sort" $ eat "*" >> Level <$> L.decimal
pSort :: Parser Expr pSort :: Parser IRExpr
pSort = try pNumSort <|> pStar pSort = lexeme $ try pNumSort <|> pStar
checkAscription :: Text -> Expr -> Maybe Expr -> Parser () pAxiom :: Parser IRDef
checkAscription ident value massert = do pAxiom = lexeme $ label "axiom" $ do
env <- get
ty <- either (customFailure . TE) pure (checkType (env ^. envL) value)
case massert of
Nothing -> updateStateDefinition ident ty value
Just assert -> do
equiv <- either (customFailure . TE) pure (checkBeta (env ^. envL) ty assert)
unless equiv $ customFailure $ TE $ NotEquivalent ty assert value
updateStateDefinition ident assert value
updateStateDefinition :: Text -> Expr -> Expr -> Parser ()
updateStateDefinition ident ty value = do
env <- get
when (M.member ident (env ^. envL)) (customFailure $ TE $ DuplicateDefinition ident)
modify $ over envL $ M.insert ident $ makeDef ty value
pAxiom :: Text -> Maybe Expr -> Parser ()
pAxiom ident Nothing = customFailure $ TE $ PNMissingType ident
pAxiom ident (Just ascription) = do
eat "axiom" eat "axiom"
eat ";"
updateStateDefinition ident ascription (Axiom ident)
pBody :: [(Text, Expr)] -> Text -> Maybe Expr -> Parser ()
pBody params ident ascription = do
value <- flip (foldr (uncurry Abs)) params <$> pExpr
checkAscription ident value ascription
eat ";"
pDef :: Parser ()
pDef = lexeme $ label "definition" $ withBinders $ do
skipSpace
ident <- pIdentifier ident <- pIdentifier
params <- pManyParams params <- pManyParams
ascription <- fmap (flip (foldr (uncurry Pi)) params) <$> pAscription ascription <- fmap (flip (foldr (uncurry Pi)) params) pAscription
eat ";"
pure $ Axiom ident ascription
pDef :: Parser IRDef
pDef = lexeme $ label "definition" $ do
eat "def"
ident <- pIdentifier
params <- pManyParams
ascription <- fmap (flip (foldr (uncurry Pi)) params) <$> optional pAscription
eat ":=" eat ":="
choice [pAxiom ident ascription, pBody params ident ascription] body <- pIRExpr
eat ";"
pure $ Def ident ascription $ foldr (uncurry Abs) body params
pTerm :: Parser Expr pIRDef :: Parser IRDef
pTerm = pIRDef = pAxiom <|> pDef
lexeme $
label "term" $
choice
[ between (char '(') (char ')') pExpr
, pSort
, pVar
]
pAppTerm :: Parser Expr pTerm :: Parser IRExpr
pAppTerm = pTerm = lexeme $ label "term" $ choice [pSort, pVar, between (char '(') (char ')') pIRExpr]
lexeme $
choice
[ pLAbs
, pPAbs
, pLet
, pApp
]
pExpr :: Parser Expr pAppTerm :: Parser IRExpr
pExpr = lexeme $ try pArrow <|> pAppTerm pAppTerm = lexeme $ choice [pLAbs, pPAbs, pLet, pApp]
pAscription :: Parser (Maybe Expr) pIRExpr :: Parser IRExpr
pAscription = lexeme $ optional $ try $ defChoice (pure ":") >> label "type" pExpr pIRExpr = lexeme $ try pArrow <|> pAppTerm
pProgram :: Parser Env pAscription :: Parser IRExpr
pProgram = lexeme $ skipSpace >> many pDef >> _env <$> get pAscription = lexeme $ try $ eat ":" >> label "type" pIRExpr
emptyBinders :: Env -> InnerState pIRProgram :: Parser IRProgram
emptyBinders env = IS{_binders = [], _env = env} pIRProgram = skipSpace >> some pIRDef
parseDef :: Text -> State Env (Either String ()) parserWrapper :: Parser a -> String -> Text -> Either String a
parseDef input = do parserWrapper p filename input = first errorBundlePretty $ runParser p filename input
env <- get
let (output, IS{_env}) = runState (runParserT pDef "" input) (emptyBinders env)
put _env
pure $ first errorBundlePretty output
parseExpr :: Env -> Text -> Either String Expr parseProgram :: String -> Text -> Either String IRProgram
parseExpr env input = first errorBundlePretty $ evalState (runParserT pExpr "" input) $ emptyBinders env parseProgram = parserWrapper pIRProgram
parseDefEmpty :: Env -> Text -> (Either String (), Env) parseDef :: String -> Text -> Either String IRDef
parseDefEmpty env input = runState (parseDef input) env parseDef = parserWrapper pIRDef
parseProgram :: Env -> Text -> Either String Env parseExpr :: String -> Text -> Either String IRExpr
parseProgram initial input = first errorBundlePretty $ evalState (runParserT pProgram "" input) $ emptyBinders initial parseExpr = parserWrapper pIRExpr
handleFile :: Env -> String -> ExceptT String IO Env handleFile :: String -> ExceptT String IO IRProgram
handleFile initial filename = (toString `withExceptT` runPreprocessor filename) >>= liftEither . parseProgram initial handleFile filename = toString `withExceptT` runPreprocessor filename >>= hoistEither . parseProgram filename

49
lib/Program.hs Normal file
View file

@ -0,0 +1,49 @@
module Program where
import Check
import Control.Monad.Except
import qualified Data.Map.Strict as M
import Elaborator
import Errors
import Eval (Env, checkBeta)
import qualified Eval
import Expr (Expr)
import qualified Expr
import IR
import Parser (parseProgram)
import Preprocessor (runPreprocessor)
insertDef :: Text -> Expr -> Expr -> Env -> Env
insertDef name ty body = M.insert name (Eval.Def ty body)
handleDef :: IRDef -> StateT Env Result ()
handleDef (Axiom name ty) = do
env <- get
_ <- liftEither $ checkType env $ elaborate ty
modify $ insertDef name (elaborate ty) (Expr.Axiom name)
handleDef (Def name Nothing irBody) = do
env <- get
ty <- liftEither $ checkType env body
modify $ insertDef name ty body
where
body = elaborate irBody
handleDef (Def name (Just irTy) irBody) = do
env <- get
ty' <- liftEither $ checkType env body
liftEither $ checkBeta env ty ty' body
modify $ insertDef name ty' body
where
body = elaborate irBody
ty = elaborate irTy
evalDef :: Env -> IRDef -> Result Env
evalDef = flip (execStateT . handleDef)
handleProgram :: Env -> IRProgram -> Result Env
handleProgram env = flip execStateT env . mapM_ handleDef
handleAndParseProgram :: Env -> String -> Text -> Either String Env
handleAndParseProgram env filename input = (first toString . handleProgram env) =<< parseProgram filename input
handleAndParseFile :: Env -> String -> ExceptT String IO Env
handleAndParseFile env filename = toString `withExceptT` runPreprocessor filename >>= hoistEither . handleAndParseProgram env filename

View file

@ -26,25 +26,30 @@ common warnings
library perga-lib library perga-lib
import: warnings import: warnings
exposed-modules: Check exposed-modules: Check
Parser Elaborator
Expr
Eval
Errors Errors
Eval
Expr
IR
Parser
Preprocessor Preprocessor
Program
hs-source-dirs: lib hs-source-dirs: lib
build-depends: base ^>=4.19.1.0 build-depends: base ^>=4.19.1.0
, relude , relude
, mtl
, megaparsec
, parser-combinators
, filepath , filepath
, megaparsec
, mtl
, parser-combinators
mixins: base hiding (Prelude) mixins: base hiding (Prelude)
, relude (Relude as Prelude) , relude (Relude as Prelude)
, relude , relude
default-language: Haskell2010 default-language: Haskell2010
default-extensions: OverloadedStrings default-extensions: OverloadedStrings
, GADTs , GADTs
, DuplicateRecordFields
, OverloadedRecordDot
executable perga executable perga
import: warnings import: warnings
@ -53,11 +58,11 @@ executable perga
build-depends: base ^>=4.19.1.0 build-depends: base ^>=4.19.1.0
, relude , relude
, perga-lib
, haskeline
, mtl
, directory , directory
, filepath , filepath
, haskeline
, mtl
, perga-lib
mixins: base hiding (Prelude) mixins: base hiding (Prelude)
, relude (Relude as Prelude) , relude (Relude as Prelude)
, relude , relude