elsa 0.2.2.0 → 0.3.0.0
raw patch · 6 files changed
+587/−124 lines, 6 filesdep ~ansi-terminaldep ~arraydep ~dequeuePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: ansi-terminal, array, dequeue, directory, filepath, hashable, json, megaparsec, mtl, unordered-containers
API changes (from Hackage documentation)
- Language.Elsa.Types: AlphEq :: a -> Eqn a
- Language.Elsa.Types: BetaEq :: a -> Eqn a
- Language.Elsa.Types: DefnEq :: a -> Eqn a
- Language.Elsa.Types: NormEq :: a -> Eqn a
- Language.Elsa.Types: TrnsEq :: a -> Eqn a
- Language.Elsa.Types: UnBeta :: a -> Eqn a
- Language.Elsa.Types: UnTrEq :: a -> Eqn a
+ Language.Elsa.Types: Conf :: EvalKind
+ Language.Elsa.Types: DefnItem :: Defn a -> ElsaItem a
+ Language.Elsa.Types: EqAlpha :: EqnOp
+ Language.Elsa.Types: EqAppOrd :: EqnOp
+ Language.Elsa.Types: EqAppOrdTrans :: EqnOp
+ Language.Elsa.Types: EqBeta :: EqnOp
+ Language.Elsa.Types: EqDefn :: EqnOp
+ Language.Elsa.Types: EqEta :: EqnOp
+ Language.Elsa.Types: EqNormOrd :: EqnOp
+ Language.Elsa.Types: EqNormOrdTrans :: EqnOp
+ Language.Elsa.Types: EqNormTrans :: EqnOp
+ Language.Elsa.Types: EqTrans :: EqnOp
+ Language.Elsa.Types: EqUnAppOrd :: EqnOp
+ Language.Elsa.Types: EqUnAppOrdTrans :: EqnOp
+ Language.Elsa.Types: EqUnBeta :: EqnOp
+ Language.Elsa.Types: EqUnEta :: EqnOp
+ Language.Elsa.Types: EqUnNormOrd :: EqnOp
+ Language.Elsa.Types: EqUnNormOrdTrans :: EqnOp
+ Language.Elsa.Types: EqUnTrans :: EqnOp
+ Language.Elsa.Types: Eqn :: EqnOp -> Maybe NormCheck -> a -> Eqn a
+ Language.Elsa.Types: EvalItem :: Eval a -> ElsaItem a
+ Language.Elsa.Types: Head :: NormCheck
+ Language.Elsa.Types: Regular :: EvalKind
+ Language.Elsa.Types: Strong :: NormCheck
+ Language.Elsa.Types: Weak :: NormCheck
+ Language.Elsa.Types: [evKind] :: Eval a -> EvalKind
+ Language.Elsa.Types: data ElsaItem a
+ Language.Elsa.Types: data EqnOp
+ Language.Elsa.Types: data EvalKind
+ Language.Elsa.Types: data NormCheck
+ Language.Elsa.Types: instance GHC.Classes.Eq Language.Elsa.Types.EqnOp
+ Language.Elsa.Types: instance GHC.Classes.Eq Language.Elsa.Types.EvalKind
+ Language.Elsa.Types: instance GHC.Classes.Eq Language.Elsa.Types.NormCheck
+ Language.Elsa.Types: instance GHC.Show.Show Language.Elsa.Types.EqnOp
+ Language.Elsa.Types: instance GHC.Show.Show Language.Elsa.Types.EvalKind
+ Language.Elsa.Types: instance GHC.Show.Show Language.Elsa.Types.NormCheck
+ Language.Elsa.Types: type SElsaItem = ElsaItem SourceSpan
- Language.Elsa.Types: Eval :: !Bind a -> !Expr a -> [Step a] -> Eval a
+ Language.Elsa.Types: Eval :: EvalKind -> !Bind a -> !Expr a -> [Step a] -> Eval a
- Language.Elsa.Types: class Tagged t
+ Language.Elsa.Types: class Tagged (t :: Type -> Type)
Files
- CHANGES.md +12/−0
- README.md +174/−34
- elsa.cabal +11/−11
- src/Language/Elsa/Eval.hs +279/−41
- src/Language/Elsa/Parser.hs +60/−11
- src/Language/Elsa/Types.hs +51/−27
CHANGES.md view
@@ -1,5 +1,17 @@ # Changes +0.3.0.0 ++- bump to GHC 9.8.4 by @ilanashapiro++- new evaluation steps and strategies by @JRB-Prod-UVA+ - A new operator =e> for the eta reduction has been added.+ - Definitions introduced with let and the evaluation or confirmation statements can now be used interchangeably. So after an evaluation or confirmation block a new let binding can be introduced.+ - Reduction and equivalence checking sequence that do not have to end in a strong normal form are now also supported, by replacing the keyword `eval` with `conf`+ - Different normal form checks on arbitrary reduction and equivalence proof checking results are now supported.+ - Support for two specific reduction strategies: normal order and applicative order were added. For this, we introduced two new operators (`=n>` and `=p>`).++ 0.2.2.0 - Faster (and correct!) implementation of Normalization by Mark Barbone (@mb64)
README.md view
@@ -11,8 +11,9 @@ ## Online Demo -You can try `elsa` online at [this link](http://goto.ucsd.edu/elsa/index.html)+You can try `elsa` online at [this link](https://elsa.goto.ucsd.edu/index.html) + ## Install You can locally build and run `elsa` by@@ -31,7 +32,9 @@ ``` ## Editor Plugins -- [VSCode](https://github.com/mistzzt/vscode-elsa-lang)+- [VS Code extension](https://marketplace.visualstudio.com/items?itemName=akainth015.elsa-lang) with syntax highlighting and autocompletion support+ - [Source](https://github.com/akainth015/vscode-elsa-lang)+ - Contributed by [**@akainth015**](https://github.com/akainth015/), based on the [original version](https://github.com/mistzzt/vscode-elsa-lang) by [**@mistzzt**](https://github.com/mistzzt) - [Vim](https://github.com/glapa-grossklag/elsa.vim) ## Overview@@ -51,7 +54,7 @@ =d> zero -- expand definitions eval id_zero_tr :- id zero + id zero =*> zero -- transitive reductions ``` @@ -63,6 +66,82 @@ OK id_zero, id_zero_tr. ``` +## Operators and Normal Form Checking++Elsa supports several operators with optional normal form checking:++### Basic Operators+- `=a>` - alpha equivalence+- `=b>` - single beta reduction+- `=e>` - single eta reduction+- `=d>` - definition expansion+- `=n>` - normal order beta reduction+- `=p>` - applicative order beta reduction+- `=*>` - transitive closure of reductions++### Normal Form Extensions+All operators can be extended with normal form checks:+- `=op:s>` - check strong normal form after operation+- `=op:w>` - check weak normal form after operation+- `=op:h>` - check head normal form after operation++Examples:+```haskell+-- nf_0.lc+let id = \z -> z++-- Check beta reduction to weak normal form+conf example1:+ (\x y -> x (\w -> w w)) id+ =b:w> (\y -> id (\w -> w w))++-- Check beta reduction to head normal form+conf example2:+ ((\x -> x) Z) (\x y -> x (\w -> w w)) id+ =b:h> Z (\x y -> x (\w -> w w)) id++-- Normal order reduction to strong normal form+conf example3:+ (\x y -> x) id+ =n:s> \y -> id+```++### Strategy-Specific Transitive Reductions+- `=n*>` - normal order transitive reductions+- `=p*>` - applicative order transitive reductions++Example:+```haskell+-- sptr_0.lc++-- The numbers 0, 1, 3, 6 in church encoding+let c0 = \f x -> x+let c1 = \f x -> f x+let c3 = \f x -> f (f (f x))+let c6 = \f x -> f (f (f (f (f (f x)))))++-- Boolean functions+let true = \x y -> x+let false = \x y -> y++-- Number operations+let iszero = \n -> n (\x -> false) true+let pred = \n f x -> n (\g h -> h (g f)) (\u -> x) (\u -> u)+let mult = \m n f x -> m (n f) x++-- Fixed-point combinator and recursive function+let Y = \g -> (\x -> g (x x)) (\x -> g (x x))+let G = \f n -> iszero n c1 (mult n (f (pred n)))+let fact = Y G++eval factorial:+ fact c3+ -- The next line shows that we can show specific intermediate steps and leave out the rest+ =n*> iszero c3 c1 (mult c3 (((\x -> G (x x)) (\x -> G (x x))) (pred c3)))+ =n*> c6 --In this case, using =~> also works++```+ ## Partial Evaluation If instead you write a partial sequence of@@ -110,6 +189,22 @@ =d> two -- optional ``` +Or you can change evaluation method, by changing+`eval` to `conf` (see also next section)++```haskell+-- succ_1_alt.lc+let one = \f x -> f x+let two = \f x -> f (f x)+let incr = \n f x -> f (n f x)++conf succ_one :+ incr one+ =d> (\n f x -> f (n f x)) (\f x -> f x)+ =b> \f x -> f ((\f x -> f x) f x)+ =b> \f x -> f ((\x -> f x) x)+```+ Similarly, `elsa` rejects the following program, ```haskell@@ -137,16 +232,32 @@ You can fix the error by inserting the appropriate intermediate term as shown in `id_0.lc` above. +## Confirmation Statements++The `conf` statement works like `eval` but+doesn't require the final term to be in normal+form. This is useful for infinite reductions+or intermediate proofs.++Example:+```haskell+-- om_0.lc+let omega = (\x -> x x) (\x -> x x)++conf omega_reduces_to_self:+ omega+ =d> (\x -> x x) (\x -> x x)+ =b> (\x -> x x) (\x -> x x)+ =d> omega+```+ ## Syntax of `elsa` Programs An `elsa` program has the form ```haskell--- definitions-[let <id> = <term>]+---- reductions-[<reduction>]*+-- definitions and evaluations can be mixed+ ([let <id> = <term>] | [<reduction>])* ``` where the basic elements are lambda-calulus `term`s@@ -156,8 +267,7 @@ \ <id>+ -> <term> (<term> <term>) ```--and `id` are lower-case identifiers +and `id` are lower-case identifiers ``` <id> ::= x, y, z, ...@@ -167,69 +277,99 @@ with a `<step>` ```haskell-<reduction> ::= eval <id> : <term> (<step> <term>)*+<reduction> ::= (eval | conf) <id> : <term> (<step> <term>)* -<step> ::= =a> -- alpha equivalence- =b> -- beta equivalence- =d> -- def equivalence- =*> -- trans equivalence- =~> -- normalizes to+<step> ::= =, <equivtype>, [:, <nfcheck>], >++<equivtype> ::= a -- alpha equivalence+ b -- beta equivalence+ e -- eta equivalence+ d -- def equivalence+ * -- trans equivalence+ n -- normal order beta equivalence+ p -- applicative order beta equivalence+ n* -- normal order trans beta equivalence+ p* -- applicative order trans beta equivalence+ ~ -- normalizes to++<nfcheck> ::= s -- strong normal form check+ w -- weak normal form check+ h -- head normal form check ``` ## Semantics of `elsa` programs -A `reduction` of the form `t_1 s_1 t_2 s_2 ... t_n` is **valid** if+An `eval` `reduction` of the form `t_1 s_1 t_2 s_2 ... t_n` is **valid** if * Each `t_i s_i t_i+1` is **valid**, and * `t_n` is in normal form (i.e. cannot be further beta-reduced.) -Furthermore, a `step` of the form +Furthermore, a `step` of the form * `t =a> t'` is valid if `t` and `t'` are equivalent up to **alpha-renaming**, * `t =b> t'` is valid if `t` **beta-reduces** to `t'` in a single step, * `t =d> t'` is valid if `t` and `t'` are identical after **let-expansion**. * `t =*> t'` is valid if `t` and `t'` are in the reflexive, transitive closure- of the union of the above three relations.-* `t =~> t'` is valid if `t` [normalizes to][normalform] `t'`.+ of the union of the above three relations,+* `t =n> t'` is valid if `t` **beta-reduces** using normal order to `t'` in+ a single step,+* `t =p> t'` is valid if `t` **beta-reduces** using applicative order to `t'`+ in a single step,+* `t =n*> t'` is valid if `t` and `t'` are in the reflexive, transitive closure+ of the union of the `=a>`, `=d>` and `=n>` operator relations,+* `t =p*> t'` is valid if `t` and `t'` are in the reflexive, transitive closure+ of the union of the `=a>`, `=d>` and `=p>` operator relations,+* `t =~> t'` is valid if `t` [normalizes to][normalform] `t'`,+* `t =e> t'` is valid if `t` **eta-reduces** to `t'` in a single step. +A `conf` `reduction` of the form `t_1 s_1 t_2 s_2 ... t_n` is similar to the+`eval` `reduction` of the same form, except that `t_n` *does not* have to be+in normal form. +Each `reduction` supports an optional `nfcheck`, which specifically checks+whether the operator is in the requested normal form, in addition to checking+the functionality of the operator. For example, `t =b:w> t'` not only checks+whether `t` can be reduced to `t'` in a single step, but also whether the+result is in weak normal form.++ (Due to Michael Borkowski) The difference between `=*>` and `=~>` is as follows. -* `t =*> t'` is _any_ sequence of zero or more steps from `t` to `t'`. - So if you are working forwards from the start, backwards from the end, - or a combination of both, you could use `=*>` as a quick check to see - if you're on the right track. +* `t =*> t'` is _any_ sequence of zero or more steps from `t` to `t'`.+ So if you are working forwards from the start, backwards from the end,+ or a combination of both, you could use `=*>` as a quick check to see+ if you're on the right track. -* `t =~> t'` says that `t` reduces to `t'` in zero or more steps **and** - that `t'` is in **normal form** (i.e. `t'` cannot be reduced further). - This means you can only place it as the *final step*. +* `t =~> t'` says that `t` reduces to `t'` in zero or more steps **and**+ that `t'` is in **normal form** (i.e. `t'` cannot be reduced further).+ This means you can only place it as the *final step*. So `elsa` would accept these three ``` eval ex1:- (\x y -> x y) (\x -> x) b + (\x y -> x y) (\x -> x) b =*> b eval ex2:- (\x y -> x y) (\x -> x) b + (\x y -> x y) (\x -> x) b =~> b eval ex3:- (\x y -> x y) (\x -> x) (\z -> z) - =*> (\x -> x) (\z -> z) + (\x y -> x y) (\x -> x) (\z -> z)+ =*> (\x -> x) (\z -> z) =b> (\z -> z) ``` -but `elsa` would *not* accept +but `elsa` would *not* accept ``` eval ex3:- (\x y -> x y) (\x -> x) (\z -> z) - =~> (\x -> x) (\z -> z) + (\x y -> x y) (\x -> x) (\z -> z)+ =~> (\x -> x) (\z -> z) =b> (\z -> z) ```
elsa.cabal view
@@ -1,5 +1,5 @@ name: elsa-version: 0.2.2.0+version: 0.3.0.0 synopsis: A tiny language for understanding the lambda-calculus description: elsa is a small proof checker for verifying sequences of reductions of lambda-calculus terms. The goal is to help@@ -31,16 +31,16 @@ Default-Extensions: OverloadedStrings build-depends: base >= 4 && < 5,- array,- mtl,- megaparsec >= 7.0.4,- ansi-terminal,- hashable,- unordered-containers,- directory,- filepath,- dequeue,- json+ array >= 0.5.8 && < 1,+ mtl >= 2.3.1 && < 3,+ megaparsec >= 9.7.0 && < 10,+ ansi-terminal >= 1.1.2 && < 2,+ hashable >= 1.5.0 && < 2,+ unordered-containers >= 0.2.20 && < 1,+ directory >= 1.3.9 && < 2,+ filepath >= 1.5.4 && < 2,+ dequeue >= 0.1.12 && < 1,+ json >= 0.11 && < 1 hs-source-dirs: src default-language: Haskell2010
src/Language/Elsa/Eval.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings, BangPatterns #-}+{-# LANGUAGE OverloadedStrings, BangPatterns, ScopedTypeVariables #-} module Language.Elsa.Eval (elsa, elsaOn) where @@ -7,9 +7,11 @@ import qualified Data.HashSet as S import qualified Data.List as L import Control.Monad.State+import Control.Monad (foldM) import qualified Data.Maybe as Mb -- (isJust, maybeToList) import Language.Elsa.Types import Language.Elsa.Utils (qPushes, qInit, qPop, fromEither)+import Data.List (group) -------------------------------------------------------------------------------- elsa :: Elsa a -> [Result a]@@ -32,7 +34,7 @@ checkDupEval = foldM addEvalId S.empty addEvalId :: S.HashSet Id -> Eval a -> CheckM a (S.HashSet Id)-addEvalId s e = +addEvalId s e = if S.member (bindId b) s then Left (errDupEval b) else Right (S.insert (bindId b) s)@@ -46,8 +48,8 @@ mkEnv = foldM expand M.empty expand :: Env a -> Defn a -> CheckM a (Env a)-expand g (Defn b e) = - if dupId +expand g (Defn b e) =+ if dupId then Left (errDupDefn b) else case zs of (x,l) : _ -> Left (Unbound b x l)@@ -65,12 +67,17 @@ -------------------------------------------------------------------------------- eval :: Env a -> Eval a -> CheckM a (Result a) ---------------------------------------------------------------------------------eval g (Eval n e steps) = go e steps+eval g (Eval kind n e steps) = go e steps where go e []- | isNormal g e = return (OK n)- | otherwise = Left (errPartial n e)- go e (s:steps) = step g n e s >>= (`go` steps)+ | noCheck kind || isNormal g e = return (OK n)+ | otherwise = Left (errPartial n e)+ go e (s:steps) = step g n e s >>= (`go` steps)+ -- Regular is just "eval", then there is always a strong normal form check+ -- at the end+ noCheck Regular = False+ -- Similar to "Regular" but without a strong normal form check at the end+ noCheck Conf = True step :: Env a -> Bind a -> Expr a -> Step a -> CheckM a (Expr a) step g n e (Step k e')@@ -78,23 +85,37 @@ | otherwise = Left (errInvalid n e k e') isEq :: Eqn a -> Env a -> Expr a -> Expr a -> Bool-isEq (AlphEq _) = isAlphEq-isEq (BetaEq _) = isBetaEq-isEq (UnBeta _) = isUnBeta-isEq (DefnEq _) = isDefnEq-isEq (TrnsEq _) = isTrnsEq-isEq (UnTrEq _) = isUnTrEq-isEq (NormEq _) = isNormEq-+isEq (Eqn op chk _) =+ case op of+ EqAlpha -> isAlphEq chk+ EqBeta -> isBetaEq chk+ EqEta -> isEtaaEq chk+ EqDefn -> isDefnEq chk+ EqNormOrd -> isNBetaEq chk+ EqAppOrd -> isABetaEq chk+ EqTrans -> isTrnsEq chk+ EqNormTrans -> toNormEq --chk unnecessary+ EqNormOrdTrans -> isNTrnsEq chk+ EqAppOrdTrans -> isATrnsEq chk+ EqUnBeta -> isUnBeta+ EqUnEta -> isUnEtaa+ EqUnNormOrd -> isUnNBeta+ EqUnAppOrd -> isUnABeta+ EqUnTrans -> isUnTrEq+ EqUnNormOrdTrans -> isUnNTrEq+ EqUnAppOrdTrans -> isUnATrEq -------------------------------------------------------------------------------- -- | Transitive Reachability ---------------------------------------------------------------------------------isTrnsEq :: Env a -> Expr a -> Expr a -> Bool-isTrnsEq g e1 e2 = Mb.isJust (findTrans (isEquiv g e2) (canon g e1))+isTrnsEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isTrnsEq Nothing g e1 e2 = Mb.isJust (findTrans (isEquiv g e2) (canon g e1))+isTrnsEq (Just Strong) g e1 e2 = isTnsSEq isNormEq g e1 e2+isTrnsEq (Just Weak) g e1 e2 = isTnsSEq isWnfEq g e1 e2+isTrnsEq (Just Head) g e1 e2 = isTnsSEq isHnfEq g e1 e2 isUnTrEq :: Env a -> Expr a -> Expr a -> Bool-isUnTrEq g e1 e2 = isTrnsEq g e2 e1+isUnTrEq g e1 e2 = isTrnsEq Nothing g e2 e1 findTrans :: (Expr a -> Bool) -> Expr a -> Maybe (Expr a) findTrans p e = go S.empty (qInit e)@@ -107,18 +128,74 @@ then return e else go (S.insert e seen) (qPushes q (betas e)) +-- findTrans with selected normal form check+isTnsSEq :: (Env a -> Expr a -> Expr a -> Bool) -> Env a -> Expr a -> Expr a -> Bool+isTnsSEq isNfEq g e1 e2 = maybe False (flip (isNfEq g) e2) (findTrans (isEquiv g e2) (canon g e1))++-- Multiple normal order beta, alpha reductions and/or definitions+isNTrnsEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isNTrnsEq Nothing = isSTrnsEq norStep+isNTrnsEq (Just Strong) = isSTrnsSEq norStep isNormEq+isNTrnsEq (Just Weak) = isSTrnsSEq norStep isWnfEq+isNTrnsEq (Just Head) = isSTrnsSEq norStep isHnfEq++isUnNTrEq :: Env a -> Expr a -> Expr a -> Bool+isUnNTrEq g e1 e2 = isNTrnsEq Nothing g e2 e1++-- Multiple applicative order beta, alpha reductions and/or definitions+isATrnsEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isATrnsEq Nothing = isSTrnsEq appStep+isATrnsEq (Just Strong) = isSTrnsSEq appStep isNormEq+isATrnsEq (Just Weak) = isSTrnsSEq appStep isWnfEq+isATrnsEq (Just Head) = isSTrnsSEq appStep isHnfEq++isUnATrEq :: Env a -> Expr a -> Expr a -> Bool+isUnATrEq g e1 e2 = isATrnsEq Nothing g e2 e1++-- Multiple beta, alpha reductions and/or definitions, using selected strategy+isSTrnsEq :: forall a. (Expr a -> Maybe (Expr a)) -> Env a -> Expr a -> Expr a -> Bool+isSTrnsEq step g e1 e2 = Mb.isJust (findSTrans step (isEquiv g e2) (canon g e1))++findSTrans :: (Expr a -> Maybe (Expr a)) -> (Expr a -> Bool) -> Expr a -> Maybe (Expr a)+findSTrans step f e = do+ if f e then -- Maybe no reductions are needed+ return e+ else do -- One or more reductions are needed+ e' <- step e+ if f e' then+ return e'+ else+ findSTrans step f e'++-- isSTrnsEq with selected normal form check+isSTrnsSEq :: (Expr a -> Maybe (Expr a)) -> (Env a -> Expr a -> Expr a -> Bool) -> Env a -> Expr a -> Expr a -> Bool+isSTrnsSEq step isNfEq g e1 e2 =+ case findSTrans step (isEquiv g e2) (canon g e1) of+ Nothing -> False+ Just e1' -> isNfEq g e1' e2+ -------------------------------------------------------------------------------- -- | Definition Equivalence ---------------------------------------------------------------------------------isDefnEq :: Env a -> Expr a -> Expr a -> Bool-isDefnEq g e1 e2 = subst e1 g == subst e2 g+isDefnEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isDefnEq Nothing g e1 e2 = subst e1 g == subst e2 g+isDefnEq (Just Strong) g e1 e2 = isNormEq g e1 e2+isDefnEq (Just Weak) g e1 e2 = isWnfEq g e1 e2+isDefnEq (Just Head) g e1 e2 = isHnfEq g e1 e2 -------------------------------------------------------------------------------- -- | Alpha Equivalence ---------------------------------------------------------------------------------isAlphEq :: Env a -> Expr a -> Expr a -> Bool-isAlphEq _ e1 e2 = alphaNormal e1 == alphaNormal e2+isAlphEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isAlphEq Nothing _ e1 e2 = alphaNormal e1 == alphaNormal e2+isAlphEq (Just Strong) g e1 e2 = isAlphPEq isNormEq g e1 e2+isAlphEq (Just Weak) g e1 e2 = isAlphPEq isWnfEq g e1 e2+isAlphEq (Just Head) g e1 e2 = isAlphPEq isHnfEq g e1 e2 +-- Alpha Equivalence with provided normal form check+isAlphPEq :: (Env a -> Expr a -> Expr a -> Bool) -> Env a -> Expr a -> Expr a -> Bool+isAlphPEq isNfEq g e1 e2 = (alphaNormal e1 == alphaNormal e2) && isNfEq g e1 e2+ alphaNormal :: Expr a -> Expr a alphaNormal = alphaShift 0 @@ -165,12 +242,77 @@ -------------------------------------------------------------------------------- -- | Beta Reduction ---------------------------------------------------------------------------------isBetaEq :: Env a -> Expr a -> Expr a -> Bool-isBetaEq _ e1 e2 = or [ e1' == e2 | e1' <- betas e1 ]+-- Beta reduction, without any normal form check+isBetaEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isBetaEq Nothing _ e1 e2 = or [ e1' == e2 | e1' <- betas e1 ]+isBetaEq (Just Strong) g e1 e2 = isBetaPEq isNormEq g e1 e2+isBetaEq (Just Weak) g e1 e2 = isBetaPEq isWnfEq g e1 e2+isBetaEq (Just Head) g e1 e2 = isBetaPEq isHnfEq g e1 e2 isUnBeta :: Env a -> Expr a -> Expr a -> Bool-isUnBeta g e1 e2 = isBetaEq g e2 e1+isUnBeta g e1 e2 = isBetaEq Nothing g e2 e1 +-- Beta reduction, with provided normal form check+isBetaPEq :: (Env a -> Expr a -> Expr a -> Bool) -> Env a -> Expr a -> Expr a -> Bool+isBetaPEq isNfEq g e1 e2 = or [ isNfEq g e1' e2 | e1' <- betas e1 ]++-- Use normal order evaluation strategy+isNBetaEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isNBetaEq = isSBetaEq norStep++isUnNBeta :: Env a -> Expr a -> Expr a -> Bool+isUnNBeta g e1 e2 = isNBetaEq Nothing g e2 e1++-- Use applicative order evaluation strategy+isABetaEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isABetaEq = isSBetaEq appStep++isUnABeta :: Env a -> Expr a -> Expr a -> Bool+isUnABeta g e1 e2 = isABetaEq Nothing g e2 e1++-- Use selected order evaluation strategy+isSBetaEq :: (Expr a -> Maybe (Expr a)) -> Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isSBetaEq step Nothing g e1 e2 = step (subst e1 g) == Just (subst e2 g)+isSBetaEq step (Just Strong) g e1 e2 = case step (subst e1 g) of+ Nothing -> False+ Just e1' -> isNormEq g e1' e2+isSBetaEq step (Just Weak) g e1 e2 = case step (subst e1 g) of+ Nothing -> False+ Just e1' -> isWnfEq g e1' e2+isSBetaEq step (Just Head) g e1 e2 = case step (subst e1 g) of+ Nothing -> False+ Just e1' -> isHnfEq g e1' e2++-- norStep is a single normal order reduction+norStep :: Expr a -> Maybe (Expr a)+norStep (EVar {}) = Nothing+norStep (ELam b e l) = do+ e' <- norStep e+ return $ ELam b e' l+norStep (EApp e1@(ELam {}) e2 _) = beta e1 e2+norStep (EApp e1 e2 l) = case norStep e1 of+ Just e1' -> return $ EApp e1' e2 l+ Nothing -> case norStep e2 of+ Just e2' -> return $ EApp e1 e2' l+ Nothing -> Nothing++-- appStep is a single applicative order reduction+appStep :: Expr a -> Maybe (Expr a)+appStep (EVar {}) = Nothing+appStep (ELam b e l) = do+ e' <- appStep e+ return $ ELam b e' l+appStep (EApp e1@(ELam {}) e2 l) = case appStep e1 of+ Just e1' -> Just $ EApp e1' e2 l+ Nothing -> case appStep e2 of+ Just e2' -> Just $ EApp e1 e2' l+ Nothing -> beta e1 e2+appStep (EApp e1 e2 l) = case appStep e1 of+ Just e1' -> return $ EApp e1' e2 l+ Nothing -> case appStep e2 of+ Just e2' -> return $ EApp e1 e2' l+ Nothing -> Nothing+ isNormal :: Env a -> Expr a -> Bool isNormal g = null . betas . (`subst` g) @@ -208,30 +350,126 @@ isIn = S.member . bindId --------------------------------------------------------------------------------+-- | Eta Reduction+--------------------------------------------------------------------------------+-- Eta reduction, without any normal form check+isEtaaEq :: Maybe NormCheck -> Env a -> Expr a -> Expr a -> Bool+isEtaaEq Nothing g e1 e2 = go e1 (subst e2 g)+ where+ go e1 e2' = or [e1' == e2' | e1' <- etas g e1]+isEtaaEq (Just Strong) g e1 e2 = isEtaPEq isNormEq g e1 e2+isEtaaEq (Just Weak) g e1 e2 = isEtaPEq isWnfEq g e1 e2+isEtaaEq (Just Head) g e1 e2 = isEtaPEq isHnfEq g e1 e2++isUnEtaa :: Env a -> Expr a -> Expr a -> Bool+isUnEtaa g e1 e2 = isEtaaEq Nothing g e2 e1++-- Eta reduction, with provided normal form check+isEtaPEq :: (Env a -> Expr a -> Expr a -> Bool) -> Env a -> Expr a -> Expr a -> Bool+isEtaPEq isNfEq g e1 e2 = or [isNfEq g e1' e2 | e1' <- etas g e1]++-- Search for an eta reduction.+-- Returns the reduced formula if one can be found,+-- returns Nothing if no reductions are possible+eta :: Expr a -> Maybe (Expr a)+eta (ELam x (EApp e (EVar x' _) _) _) =+ let zs = freeVars e in+ if (bindId x == x') && not (isIn x zs)+ then+ Just e+ else Nothing+eta _ = Nothing++etas :: Env a -> Expr a -> [Expr a]+etas g e = go (subst e g)+ where+ go (EVar {}) = []+ -- Pattern where reduction might be possible+ go e'@(ELam b e1 z) = Mb.maybeToList (eta e')+ ++ [ELam b e1' z | e1' <- go e1]+ go (EApp e1 e2 z) = [EApp e1' e2 z | e1' <- go e1]+ ++ [EApp e1 e2' z | e2' <- go e2]++-------------------------------------------------------------------------------- -- | Evaluation to Normal Form --------------------------------------------------------------------------------+-- Check if e1 is strong normal form isNormEq :: Env a -> Expr a -> Expr a -> Bool-isNormEq g e1 e2 = eqVal (subst e2 g) $ evalNbE ML.empty (subst e1 g)+isNormEq g e1 e2 = (e1' == e2') && nEqVal e2' (nf e2') where- evalNbE !env e = case e of- EVar x _ -> Mb.fromMaybe (Neutral x []) $ ML.lookup x env- ELam (Bind x _) b _ -> Fun $ \val -> evalNbE (ML.insert x val env) b- EApp f arg _ -> case evalNbE env f of- Fun f' -> f' (evalNbE env arg)- Neutral x args -> Neutral x (evalNbE env arg:args)+ e1' = alphaNormal $ subst e1 g+ e2' = alphaNormal $ subst e2 g+ nf = evalNbE ML.empty - eqVal (EVar x _) (Neutral x' [])- = x == x'- eqVal (ELam (Bind x _) b _) (Fun f)- = eqVal b (f (Neutral x []))- eqVal (EApp f a _) (Neutral x (a':args))- = eqVal a a' && eqVal f (Neutral x args)- eqVal _ _ = False+toNormEq :: Env a -> Expr a -> Expr a -> Bool+toNormEq g e1 e2 = nEqVal (subst e2 g) $ evalNbE ML.empty (subst e1 g) +evalNbE :: ML.HashMap Id Value -> Expr a -> Value+evalNbE !env e = case e of+ EVar x _ -> Mb.fromMaybe (Neutral x []) $ ML.lookup x env+ ELam (Bind x _) b _ -> Fun $ \val -> evalNbE (ML.insert x val env) b+ EApp f arg _ -> case evalNbE env f of+ Fun f' -> f' (evalNbE env arg)+ Neutral x args -> Neutral x (evalNbE env arg:args)++nEqVal :: Expr a -> Value -> Bool+nEqVal (EVar x _) (Neutral x' [])+ = x == x'+nEqVal (ELam (Bind x _) b _) (Fun f)+ = nEqVal b (f (Neutral x []))+nEqVal (EApp f a _) (Neutral x (a':args))+ = nEqVal a a' && nEqVal f (Neutral x args)+nEqVal _ _ = False+ -- | NbE semantic domain data Value = Fun !(Value -> Value) | Neutral !Id ![Value] --------------------------------------------------------------------------------+-- | Evaluation to Weak Normal Form+--------------------------------------------------------------------------------+isWnfEq :: Env a -> Expr a -> Expr a -> Bool+isWnfEq g e1 e2 = (e1' == e2') && (e2' == wnf e2')+ where+ e1' = alphaNormal $ subst e1 g+ e2' = alphaNormal $ subst e2 g+ wnf :: Expr a -> Expr a+ wnf e@(EVar {}) = e+ wnf e@(ELam {}) = e+ wnf (EApp f arg l) = case wnf f of+ f'@ELam {} -> maybe (EApp f' (wnf arg) l) wnf (beta f $ wnf arg)+ f' -> EApp f' (wnf arg) l++--------------------------------------------------------------------------------+-- | Evaluation to Head Normal Form+--------------------------------------------------------------------------------+isHnfEq :: Env a -> Expr a -> Expr a -> Bool+isHnfEq g e1 e2 = (e1' == e2') && (e2' == hnf e2')+ where+ e1' = alphaNormal $ subst e1 g+ e2' = alphaNormal $ subst e2 g+ hnf :: Expr a -> Expr a+ hnf e@(EVar {}) = e+ hnf (ELam bi b a) = ELam bi (hnf b) a+ hnf (EApp f arg l) = case hnf f of+ f'@ELam {} -> maybe (EApp f' (hnf arg) l) hnf (beta f' arg)+ f' -> EApp f' arg l++--------------------------------------------------------------------------------+-- | Evaluation to Weak Head Normal Form+--------------------------------------------------------------------------------+{- isWhnfEq :: Env a -> Expr a -> Expr a -> Bool+isWhnfEq g e1 e2 = (e1' == e2') && (e2' == whnf e2')+ where+ e1' = subst e1 g+ e2' = subst e2 g+ whnf :: Expr a -> Expr a+ whnf e@(EVar {}) = e+ whnf e@(ELam {}) = e+ whnf (EApp f arg l) = case whnf f of+ f'@ELam {} -> maybe (EApp f' arg l) whnf (beta f arg)+ f' -> EApp f' arg l -}++-------------------------------------------------------------------------------- -- | General Helpers -------------------------------------------------------------------------------- freeVars :: Expr a -> S.HashSet Id@@ -253,7 +491,7 @@ canon g = alphaNormal . (`subst` g) isEquiv :: Env a -> Expr a -> Expr a -> Bool-isEquiv g e1 e2 = isAlphEq g (subst e1 g) (subst e2 g)+isEquiv g e1 e2 = isAlphEq Nothing g (subst e1 g) (subst e2 g) -------------------------------------------------------------------------------- -- | Error Cases --------------------------------------------------------------------------------
src/Language/Elsa/Parser.hs view
@@ -103,7 +103,7 @@ -- | list of reserved words keywords :: [Text]-keywords = [ "let" , "eval" ]+keywords = [ "let" , "eval" , "conf" ] -- | `identifier` parses identifiers: lower-case alphabets followed by alphas or digits identifier :: Parser (String, SourceSpan)@@ -140,8 +140,18 @@ -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- elsa :: Parser SElsa-elsa = Elsa <$> many defn <*> many eval+elsa = do+ items <- many elsaItem+ pure $+ Elsa+ { defns = [d | DefnItem d <- items],+ evals = [e | EvalItem e <- items]+ } +elsaItem :: Parser SElsaItem+elsaItem = + (DefnItem <$> defn) <|> (EvalItem <$> eval)+ defn :: Parser SDefn defn = do rWord "let"@@ -151,24 +161,63 @@ eval :: Parser SEval eval = do- rWord "eval"+ kind <- (rWord "eval" >> return Regular) <|> (rWord "conf" >> return Conf) name <- binder colon root <- expr steps <- many step- return $ Eval name root steps+ return $ Eval kind name root steps step :: Parser SStep step = Step <$> eqn <*> expr eqn :: Parser SEqn-eqn = try (withSpan' (symbol "=a>" >> return AlphEq))- <|> try (withSpan' (symbol "=b>" >> return BetaEq))- <|> try (withSpan' (symbol "<b=" >> return UnBeta))- <|> try (withSpan' (symbol "=d>" >> return DefnEq))- <|> try (withSpan' (symbol "=*>" >> return TrnsEq))- <|> try (withSpan' (symbol "<*=" >> return UnTrEq))- <|> (withSpan' (symbol "=~>" >> return NormEq))+eqn = withSpan' parseEqn++parseEqn :: Parser (SourceSpan -> Eqn SourceSpan)+parseEqn = try parseUnEqn <|> parseRegEqn++parseUnEqn :: Parser (SourceSpan -> Eqn SourceSpan)+parseUnEqn = do+ void $ char '<'+ op <- choice+ [ try (symbol "n*=") >> return EqUnNormOrdTrans+ , try (symbol "p*=") >> return EqUnAppOrdTrans+ , try (symbol "b=") >> return EqUnBeta+ , try (symbol "n=") >> return EqUnNormOrd+ , try (symbol "p=") >> return EqUnAppOrd+ , try (symbol "e=") >> return EqUnEta+ , try (symbol "*=") >> return EqUnTrans+ ]+ return $ \sp -> Eqn op Nothing sp++parseRegEqn :: Parser (SourceSpan -> Eqn SourceSpan)+parseRegEqn = do+ void $ char '='+ op <- choice+ [ try (string "n*") >> return EqNormOrdTrans+ , try (string "p*") >> return EqAppOrdTrans+ , try (string "n") >> return EqNormOrd+ , try (string "p") >> return EqAppOrd+ , try (string "a") >> return EqAlpha+ , try (string "b") >> return EqBeta+ , try (string "e") >> return EqEta+ , try (string "d") >> return EqDefn+ , try (string "*") >> return EqTrans+ , try (string "~") >> return EqNormTrans+ ]+ mChk <- optional parseNormCheck+ void $ symbol ">"+ return $ \sp -> Eqn op mChk sp++parseNormCheck :: Parser NormCheck+parseNormCheck = do+ void $ char ':'+ choice+ [ char 's' >> return Strong+ , char 'w' >> return Weak+ , char 'h' >> return Head+ ] expr :: Parser SExpr expr = try lamExpr
src/Language/Elsa/Types.hs view
@@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE InstanceSigs #-} module Language.Elsa.Types where @@ -11,15 +12,16 @@ import Data.Maybe (mapMaybe) import Data.Hashable -type Id = String-type SElsa = Elsa SourceSpan-type SDefn = Defn SourceSpan-type SExpr = Expr SourceSpan-type SEval = Eval SourceSpan-type SStep = Step SourceSpan-type SBind = Bind SourceSpan-type SEqn = Eqn SourceSpan-type SResult = Result SourceSpan+type Id = String+type SElsaItem = ElsaItem SourceSpan+type SElsa = Elsa SourceSpan+type SDefn = Defn SourceSpan+type SExpr = Expr SourceSpan+type SEval = Eval SourceSpan+type SStep = Step SourceSpan+type SBind = Bind SourceSpan+type SEqn = Eqn SourceSpan+type SResult = Result SourceSpan -------------------------------------------------------------------------------- -- | Result@@ -64,6 +66,8 @@ -------------------------------------------------------------------------------- -- | Programs --------------------------------------------------------------------------------+data ElsaItem a = DefnItem (Defn a) | EvalItem (Eval a)+ data Elsa a = Elsa { defns :: [Defn a] , evals :: [Eval a]@@ -74,27 +78,53 @@ = Defn !(Bind a) !(Expr a) deriving (Eq, Show) +data EvalKind = Regular | Conf deriving (Eq, Show)+ data Eval a = Eval- { evName :: !(Bind a)+ { evKind :: EvalKind+ , evName :: !(Bind a) , evRoot :: !(Expr a) , evSteps :: [Step a]- }- deriving (Eq, Show)+ } deriving (Eq, Show) data Step a = Step !(Eqn a) !(Expr a) deriving (Eq, Show) -data Eqn a- = AlphEq a- | BetaEq a- | UnBeta a- | DefnEq a- | TrnsEq a- | UnTrEq a- | NormEq a+{-+ EqAlpha : Alpha equivalence+ EqBeta : Beta reduction+ EqEta : Eta reduction+ EqDefn : Definition unpacking+ EqNormOrd : Normal order beta reduction+ EqAppOrd : Applicative order beta reduction+ EqNormOrdTrans : Normal order beta reduction with alpha equivalence and definition unpacking+ EqAppOrdTrans : Applicative order beta reduction with alpha equivalence and definition unpacking+ EqTrans : Zero or more beta reductions with alpha equivalence and definition unpacking+ EqNormTrans : Acts the same as the "=n*:s>" operator, no matter the normal form check+ EqUnBeta : Backwards beta reduction+ EqUnEta : Backwards eta reduction+ EqUnNormOrd : Backwards normal order beta reduction+ EqUnAppOrd : Backwards applicative order beta reduction+ EqUnTrans : Backwards zero or more beta reductions with alpha equivalence and definition unpacking+ EqUnNormOrdTrans: Backwards normal order beta reduction with alpha equivalence and definition unpacking+ EqUnAppOrdTrans : Backwards applicative order beta reduction with alpha equivalence and definition unpacking+-}+data EqnOp+ = EqAlpha | EqBeta | EqEta | EqDefn+ | EqNormOrd | EqAppOrd | EqTrans+ | EqNormOrdTrans | EqAppOrdTrans+ | EqNormTrans+ | EqUnBeta | EqUnEta | EqUnNormOrd+ | EqUnAppOrd | EqUnTrans+ | EqUnNormOrdTrans | EqUnAppOrdTrans deriving (Eq, Show) +-- Strong, weak, or head normal form check+data NormCheck = Strong | Weak | Head deriving (Eq, Show)++data Eqn a = Eqn EqnOp (Maybe NormCheck) a deriving (Eq, Show)+ data Bind a = Bind Id a deriving (Show, Functor)@@ -172,13 +202,7 @@ tag :: t a -> a instance Tagged Eqn where- tag (AlphEq x) = x- tag (BetaEq x) = x- tag (UnBeta x) = x- tag (DefnEq x) = x- tag (TrnsEq x) = x- tag (UnTrEq x) = x- tag (NormEq x) = x+ tag (Eqn _ _ x) = x instance Tagged Bind where tag (Bind _ x) = x