Remove `letrec` in favor of allowing any `let` expression to be recursive.
parent
b337ecb094
commit
e49be205f2
|
@ -59,8 +59,8 @@ The parser's error messages currently are virtually useless, so be very careful
|
||||||
* Function application: `f x y`
|
* Function application: `f x y`
|
||||||
* Lambda abstraction: `\x y z. E` or `λx y z. E`
|
* Lambda abstraction: `\x y z. E` or `λx y z. E`
|
||||||
* Let expressions: `let x = E; y = F in G`
|
* Let expressions: `let x = E; y = F in G`
|
||||||
* Or letrec expressions, which can only define variable,
|
* The definitions of let expessions may be recursive:
|
||||||
but which can be self-referential: `letrec x = ... x ... in E`
|
`let undefined = undefined in undefined`.
|
||||||
* Parenthetical expressions: `(E)`
|
* Parenthetical expressions: `(E)`
|
||||||
* Constructors: `()`, `(x, y)` (or `(,) x y`), `Left x`, `Right y`, `Z`, `S`, `[]`, `(x :: xs)` (or `(:) x xs`), `Char n`.
|
* Constructors: `()`, `(x, y)` (or `(,) x y`), `Left x`, `Right y`, `Z`, `S`, `[]`, `(x :: xs)` (or `(:) x xs`), `Char n`.
|
||||||
* The parentheses around the cons constructor are not optional.
|
* The parentheses around the cons constructor are not optional.
|
||||||
|
@ -76,7 +76,7 @@ The parser's error messages currently are virtually useless, so be very careful
|
||||||
* Comments: `// line comment`, `/* block comment */`
|
* Comments: `// line comment`, `/* block comment */`
|
||||||
|
|
||||||
Top-level contexts (e.g. the REPL or a source code file)
|
Top-level contexts (e.g. the REPL or a source code file)
|
||||||
allow declarations (`let(rec) x = E` without multiple definitions `in ...`),
|
allow declarations (`let x = E` without multiple definitions `in ...`),
|
||||||
which make your definitions available for the rest of the program's execution.
|
which make your definitions available for the rest of the program's execution.
|
||||||
You must separate your declarations and expressions with `;`.
|
You must separate your declarations and expressions with `;`.
|
||||||
|
|
||||||
|
|
|
@ -81,15 +81,15 @@ runProgram program = do
|
||||||
runDeclOrExpr (Right (Var "main"))
|
runDeclOrExpr (Right (Var "main"))
|
||||||
|
|
||||||
loadFile :: ProgramAST -> AppM ()
|
loadFile :: ProgramAST -> AppM ()
|
||||||
loadFile = mapM_ (\(name, ty, e) -> define name ty $ ast2check e)
|
loadFile = mapM_ (\(name, ty, e) -> define name ty $ decl2check name e)
|
||||||
|
|
||||||
runTopLevel :: TopLevelAST -> AppM ()
|
runTopLevel :: TopLevelAST -> AppM ()
|
||||||
runTopLevel = mapM_ runDeclOrExpr
|
runTopLevel = mapM_ runDeclOrExpr
|
||||||
|
|
||||||
runDeclOrExpr :: Either Declaration AST -> AppM ()
|
runDeclOrExpr :: Either Declaration AST -> AppM ()
|
||||||
runDeclOrExpr (Left (name, ty, exprAST)) = do
|
runDeclOrExpr (Left (name, ty, body)) = do
|
||||||
defs <- gets definitions
|
defs <- gets definitions
|
||||||
let expr = substitute defs $ ast2check exprAST
|
let expr = substitute defs $ decl2check name body
|
||||||
_ <- typecheckDecl ty name expr
|
_ <- typecheckDecl ty name expr
|
||||||
define name ty expr
|
define name ty expr
|
||||||
runDeclOrExpr (Right exprAST) = do
|
runDeclOrExpr (Right exprAST) = do
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env -S ivo -c
|
#!/usr/bin/env -S ivo -c
|
||||||
// Create a list by iterating `f` `n` times:
|
// Create a list by iterating `f` `n` times:
|
||||||
letrec iterate = \f x.
|
let iterate = \f x.
|
||||||
{ Z -> []
|
{ Z -> []
|
||||||
; S n -> (x :: iterate f (f x) n)
|
; S n -> (x :: iterate f (f x) n)
|
||||||
};
|
};
|
||||||
|
@ -11,13 +11,13 @@ let countToTen : [Nat] =
|
||||||
in countTo 10;
|
in countTo 10;
|
||||||
|
|
||||||
// Append two lists together:
|
// Append two lists together:
|
||||||
letrec append = \xs ys.
|
let append = \xs ys.
|
||||||
{ [] -> ys
|
{ [] -> ys
|
||||||
; (x :: xs) -> (x :: append xs ys)
|
; (x :: xs) -> (x :: append xs ys)
|
||||||
} xs;
|
} xs;
|
||||||
|
|
||||||
// Reverse a list:
|
// Reverse a list:
|
||||||
letrec reverse =
|
let reverse =
|
||||||
{ [] -> []
|
{ [] -> []
|
||||||
; (x :: xs) -> append (reverse xs) [x]
|
; (x :: xs) -> append (reverse xs) [x]
|
||||||
};
|
};
|
||||||
|
@ -31,7 +31,7 @@ let threePlusTwo : Nat =
|
||||||
; plus = \x. x Sf
|
; plus = \x. x Sf
|
||||||
in plus (\f x. f (f (f x))) (\f x. f (f x)) S Z;
|
in plus (\f x. f (f (f x))) (\f x. f (f x)) S Z;
|
||||||
|
|
||||||
letrec undefined = undefined;
|
let undefined = undefined;
|
||||||
|
|
||||||
// This expression would loop forever, but `callcc` saves the day!
|
// This expression would loop forever, but `callcc` saves the day!
|
||||||
let callccSaves : Nat = S (callcc \k. undefined (k Z));
|
let callccSaves : Nat = S (callcc \k. undefined (k Z));
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Ivo.Expression
|
module Ivo.Expression
|
||||||
( Expr (..), Ctr (..), Pat, ExprF (..), PatF (..), DefF (..), VoidF, UnitF (..), Text
|
( Expr (..), Ctr (..), Pat, ExprF (..), PatF (..), DefF (..), VoidF, UnitF (..), Text
|
||||||
, Type (..), TypeF (..), Scheme (..), tapp
|
, Type (..), TypeF (..), Scheme (..), tapp
|
||||||
, substitute, substitute1, rename, free, bound, used
|
, substitute, substitute1, rename, free, freeIn, bound, used
|
||||||
, Eval, EvalExpr, EvalX, EvalXF (..), Identity (..)
|
, Eval, EvalExpr, EvalX, EvalXF (..), Identity (..)
|
||||||
, pattern AppFE, pattern CtrE, pattern CtrFE,
|
, pattern AppFE, pattern CtrE, pattern CtrFE,
|
||||||
pattern ContE, pattern ContFE, pattern CallCCE, pattern CallCCFE
|
pattern ContE, pattern ContFE, pattern CallCCE, pattern CallCCFE
|
||||||
|
@ -11,7 +11,7 @@ module Ivo.Expression
|
||||||
, Check, CheckExpr, CheckExprF, CheckX, CheckXF (..)
|
, Check, CheckExpr, CheckExprF, CheckX, CheckXF (..)
|
||||||
, pattern AppFC, pattern CtrC, pattern CtrFC, pattern CallCCC, pattern CallCCFC
|
, pattern AppFC, pattern CtrC, pattern CtrFC, pattern CallCCC, pattern CallCCFC
|
||||||
, pattern FixC, pattern FixFC, pattern HoleC, pattern HoleFC
|
, pattern FixC, pattern FixFC, pattern HoleC, pattern HoleFC
|
||||||
, ast2check, ast2eval, check2eval, check2ast, eval2ast
|
, ast2check, decl2check, ast2eval, check2eval, check2ast, eval2ast
|
||||||
, builtins
|
, builtins
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
@ -36,9 +36,18 @@ ast2check = substitute builtins . cata \case
|
||||||
AppF ef exs -> foldl' App ef $ toList exs
|
AppF ef exs -> foldl' App ef $ toList exs
|
||||||
AbsF ns e -> foldr Abs e $ toList ns
|
AbsF ns e -> foldr Abs e $ toList ns
|
||||||
LetF ds e ->
|
LetF ds e ->
|
||||||
let letExpr name val body' = App (Abs name body') val
|
let
|
||||||
|
letExpr, letPlainExpr, letRecExpr
|
||||||
|
:: Text -> CheckExpr -> CheckExpr -> CheckExpr
|
||||||
|
-- | A let expression binding a non-recursive value.
|
||||||
|
letPlainExpr name val body' = App (Abs name body') val
|
||||||
|
-- | A let expression binding a recursive value.
|
||||||
|
letRecExpr name val body' = letExpr name (App FixC $ Abs name val) body'
|
||||||
|
-- | Choose whether or not the let expression needs to be recursive.
|
||||||
|
letExpr name val body'
|
||||||
|
| name `freeIn` val = letRecExpr name val body'
|
||||||
|
| otherwise = letPlainExpr name val body'
|
||||||
in foldr (uncurry letExpr) e $ getNonEmptyDefFs ds
|
in foldr (uncurry letExpr) e $ getNonEmptyDefFs ds
|
||||||
LetRecFP (nx, ex) e -> App (Abs nx e) (App FixC (Abs nx ex))
|
|
||||||
CtrF ctr es -> foldl' App (CtrC ctr) es
|
CtrF ctr es -> foldl' App (CtrC ctr) es
|
||||||
CaseF ps -> Case ps
|
CaseF ps -> Case ps
|
||||||
AnnF () e t -> Ann () e t
|
AnnF () e t -> Ann () e t
|
||||||
|
@ -55,6 +64,13 @@ ast2check = substitute builtins . cata \case
|
||||||
mkList :: [CheckExpr] -> CheckExpr
|
mkList :: [CheckExpr] -> CheckExpr
|
||||||
mkList = foldr (App . App (CtrC CCons)) (CtrC CNil)
|
mkList = foldr (App . App (CtrC CCons)) (CtrC CNil)
|
||||||
|
|
||||||
|
-- | Convert from declaration abstract syntax to a typechecker expression.
|
||||||
|
decl2check :: Text -> AST -> CheckExpr
|
||||||
|
decl2check name ast
|
||||||
|
| name `freeIn` ast = App FixC $ Abs name expr
|
||||||
|
| otherwise = expr
|
||||||
|
where expr = ast2check ast
|
||||||
|
|
||||||
-- | Convert from a typechecker expression to an evaluator expression.
|
-- | Convert from a typechecker expression to an evaluator expression.
|
||||||
check2eval :: CheckExpr -> EvalExpr
|
check2eval :: CheckExpr -> EvalExpr
|
||||||
check2eval = cata \case
|
check2eval = cata \case
|
||||||
|
|
|
@ -8,9 +8,10 @@ module Ivo.Expression.Base
|
||||||
, Type (..), TypeF (..), Scheme (..), tapp
|
, Type (..), TypeF (..), Scheme (..), tapp
|
||||||
, RecursivePhase, projectAppArgs, projectLetArgs, projectCtrArgs, projectXExpr, projectDef
|
, RecursivePhase, projectAppArgs, projectLetArgs, projectCtrArgs, projectXExpr, projectDef
|
||||||
, embedAppArgs, embedLetArgs, embedCtrArgs, embedXExpr, embedDef
|
, embedAppArgs, embedLetArgs, embedCtrArgs, embedXExpr, embedDef
|
||||||
, Substitutable, free, bound, used, collectVars, rename, rename1
|
, Substitutable, free, freeIn, bound, used, collectVars, rename, rename1
|
||||||
, substitute, substitute1, unsafeSubstitute, unsafeSubstitute1
|
, substitute, substitute1, unsafeSubstitute, unsafeSubstitute1
|
||||||
, runRenamer, freshVar, replaceNames, runSubstituter, maySubstitute
|
, runRenamer, freshVar, replaceNames, runSubstituter, maySubstitute
|
||||||
|
, compose, composeMap
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.Reader (MonadReader, Reader, runReader, asks, local)
|
import Control.Monad.Reader (MonadReader, Reader, runReader, asks, local)
|
||||||
|
@ -307,6 +308,10 @@ class Substitutable e where
|
||||||
free :: e -> HashSet Text
|
free :: e -> HashSet Text
|
||||||
free = collectVars HS.singleton HS.delete
|
free = collectVars HS.singleton HS.delete
|
||||||
|
|
||||||
|
-- | Is the given name a member of the expression's free variables?
|
||||||
|
freeIn :: Text -> e -> Bool
|
||||||
|
freeIn name = HS.member name . free
|
||||||
|
|
||||||
-- | Bound variables are variables which are abstracted over anywhere in an expression.
|
-- | Bound variables are variables which are abstracted over anywhere in an expression.
|
||||||
bound :: e -> HashSet Text
|
bound :: e -> HashSet Text
|
||||||
bound = collectVars (const HS.empty) HS.insert
|
bound = collectVars (const HS.empty) HS.insert
|
||||||
|
@ -430,11 +435,14 @@ maySubstitute :: ( MonadReader (HashMap Text b) m
|
||||||
)
|
)
|
||||||
=> t Text -> (a, m a) -> m a
|
=> t Text -> (a, m a) -> m a
|
||||||
maySubstitute ns (unmodified, substituted) =
|
maySubstitute ns (unmodified, substituted) =
|
||||||
local (compose $ fmap HM.delete ns) do
|
local (composeMap HM.delete ns) do
|
||||||
noMoreSubsts <- asks HM.null
|
noMoreSubsts <- asks HM.null
|
||||||
if noMoreSubsts
|
if noMoreSubsts
|
||||||
then pure unmodified
|
then pure unmodified
|
||||||
else substituted
|
else substituted
|
||||||
|
|
||||||
compose :: Foldable t => t (a -> a) -> a -> a
|
compose :: Foldable t => t (a -> a) -> (a -> a)
|
||||||
compose = foldr (.) id
|
compose = foldr (.) id
|
||||||
|
|
||||||
|
composeMap :: (Functor t, Foldable t) => (a -> (b -> b)) -> t a -> (b -> b)
|
||||||
|
composeMap f = compose . fmap f
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
module Ivo.Syntax.Base
|
module Ivo.Syntax.Base
|
||||||
( Expr (..), ExprF (..), Ctr (..), Pat, Def, DefF (..), PatF (..), VoidF, Text, NonEmpty (..)
|
( Expr (..), ExprF (..), Ctr (..), Pat, Def, DefF (..), PatF (..), VoidF, Text, NonEmpty (..)
|
||||||
, Type (..), TypeF (..), Scheme (..), tapp
|
, Type (..), TypeF (..), Scheme (..), tapp
|
||||||
, substitute, substitute1, rename, rename1, free, bound, used
|
, substitute, substitute1, rename, rename1, free, freeIn, bound, used
|
||||||
, Parse, AST, ASTF, ASTX, ASTXF (..), NonEmptyDefFs (..)
|
, Parse, AST, ASTF, ASTX, ASTXF (..), NonEmptyDefFs (..)
|
||||||
, pattern LetFP, pattern LetRecP, pattern LetRecFP
|
, pattern LetFP
|
||||||
, pattern PNat, pattern PNatF, pattern PList, pattern PListF, pattern PChar, pattern PCharF
|
, pattern PNat, pattern PNatF, pattern PList, pattern PListF, pattern PChar, pattern PCharF
|
||||||
, pattern PStr, pattern PStrF, pattern HoleP, pattern HoleFP
|
, pattern PStr, pattern PStrF, pattern HoleP, pattern HoleFP
|
||||||
, simplify
|
, simplify
|
||||||
|
@ -11,7 +11,8 @@ module Ivo.Syntax.Base
|
||||||
|
|
||||||
import Ivo.Expression.Base
|
import Ivo.Expression.Base
|
||||||
|
|
||||||
import Data.Functor.Foldable (embed, project)
|
import Data.Foldable (fold)
|
||||||
|
import Data.Functor.Foldable (embed, project, cata)
|
||||||
import Data.List.NonEmpty (NonEmpty (..), toList)
|
import Data.List.NonEmpty (NonEmpty (..), toList)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
|
|
||||||
|
@ -49,10 +50,8 @@ type instance CtrArgsF Parse = []
|
||||||
type instance XExprF Parse = ASTXF
|
type instance XExprF Parse = ASTXF
|
||||||
|
|
||||||
data ASTXF r
|
data ASTXF r
|
||||||
-- | A let expression where the definitions may reference each other recursively.
|
|
||||||
= LetRecP_ !(Text, r) !r
|
|
||||||
-- | A natural number literal, e.g. `10`.
|
-- | A natural number literal, e.g. `10`.
|
||||||
| PNat_ Int
|
= PNat_ Int
|
||||||
-- | A list literal, e.g. `[x, y, z]`.
|
-- | A list literal, e.g. `[x, y, z]`.
|
||||||
| PList_ [r]
|
| PList_ [r]
|
||||||
-- | A character literal, e.g. `'a`.
|
-- | A character literal, e.g. `'a`.
|
||||||
|
@ -73,12 +72,6 @@ newtype NonEmptyDefFs r = NonEmptyDefFs { getNonEmptyDefFs :: NonEmpty (Text, r)
|
||||||
pattern LetFP :: NonEmpty (Text, r) -> r -> ASTF r
|
pattern LetFP :: NonEmpty (Text, r) -> r -> ASTF r
|
||||||
pattern LetFP ds e = LetF (NonEmptyDefFs ds) e
|
pattern LetFP ds e = LetF (NonEmptyDefFs ds) e
|
||||||
|
|
||||||
pattern LetRecP :: (Text, AST) -> AST -> AST
|
|
||||||
pattern LetRecP d e = ExprX (LetRecP_ d e)
|
|
||||||
|
|
||||||
pattern LetRecFP :: (Text, r) -> r -> ASTF r
|
|
||||||
pattern LetRecFP d e = ExprXF (LetRecP_ d e)
|
|
||||||
|
|
||||||
pattern PNat :: Int -> AST
|
pattern PNat :: Int -> AST
|
||||||
pattern PNat n = ExprX (PNat_ n)
|
pattern PNat n = ExprX (PNat_ n)
|
||||||
|
|
||||||
|
@ -110,11 +103,26 @@ pattern HoleFP :: ASTF r
|
||||||
pattern HoleFP = ExprXF HoleP_
|
pattern HoleFP = ExprXF HoleP_
|
||||||
|
|
||||||
{-# COMPLETE VarF, AppF, AbsF, LetFP, CtrF, CaseF, AnnF, ExprXF #-}
|
{-# COMPLETE VarF, AppF, AbsF, LetFP, CtrF, CaseF, AnnF, ExprXF #-}
|
||||||
{-# COMPLETE Var, App, Abs, Let, Ctr, Case, Ann, LetRecP, PNat, PList, PChar, PStr, HoleP #-}
|
{-# COMPLETE Var, App, Abs, Let, Ctr, Case, Ann, PNat, PList, PChar, PStr, HoleP #-}
|
||||||
{-# COMPLETE VarF, AppF, AbsF, LetF , CtrF, CaseF, AnnF, LetRecFP, PNatF, PListF, PCharF, PStrF, HoleFP #-}
|
{-# COMPLETE VarF, AppF, AbsF, LetF , CtrF, CaseF, AnnF, PNatF, PListF, PCharF, PStrF, HoleFP #-}
|
||||||
{-# COMPLETE VarF, AppF, AbsF, LetFP, CtrF, CaseF, AnnF, LetRecFP, PNatF, PListF, PCharF, PStrF, HoleFP #-}
|
{-# COMPLETE VarF, AppF, AbsF, LetFP, CtrF, CaseF, AnnF, PNatF, PListF, PCharF, PStrF, HoleFP #-}
|
||||||
|
|
||||||
-- TODO: Implement Substitutable for AST.
|
instance Substitutable AST where
|
||||||
|
collectVars withVar withBinder = cata \case
|
||||||
|
VarF name -> withVar name
|
||||||
|
AbsF names body -> compose (fmap withBinder names) body
|
||||||
|
LetFP defs body ->
|
||||||
|
composeMap (\(name, def) body' ->
|
||||||
|
withBinder name def <> withBinder name body'
|
||||||
|
) defs body
|
||||||
|
CaseF pats -> foldMap (\(Pat _ ns e) -> foldr withBinder e ns) pats
|
||||||
|
e -> fold e
|
||||||
|
|
||||||
|
-- TODO
|
||||||
|
rename = error "rename not yet implemented for AST"
|
||||||
|
|
||||||
|
-- TODO
|
||||||
|
unsafeSubstitute = error "unsafeSubstitute not yet implemented for AST"
|
||||||
|
|
||||||
-- | Combine nested expressions into compound expressions or literals when possible.
|
-- | Combine nested expressions into compound expressions or literals when possible.
|
||||||
simplify :: AST -> AST
|
simplify :: AST -> AST
|
||||||
|
|
|
@ -92,11 +92,9 @@ definition = label "definition" $ do
|
||||||
pure (name, value)
|
pure (name, value)
|
||||||
|
|
||||||
let_ :: Parser AST
|
let_ :: Parser AST
|
||||||
let_ = label "let expression" $ letrecstar <|> letstar
|
let_ = label "let expression" $
|
||||||
|
Let <$> between (keyword "let") (keyword "in") definitions <*> ambiguous
|
||||||
where
|
where
|
||||||
letrecstar = LetRecP <$> between (try (keyword "letrec")) (keyword "in") definition <*> ambiguous
|
|
||||||
letstar = Let <$> between (keyword "let") (keyword "in") definitions <*> ambiguous
|
|
||||||
|
|
||||||
definitions :: Parser (NonEmpty (Def Parse))
|
definitions :: Parser (NonEmpty (Def Parse))
|
||||||
definitions = fromList <$> sepBy1 definition (token ';')
|
definitions = fromList <$> sepBy1 definition (token ';')
|
||||||
|
|
||||||
|
@ -269,15 +267,9 @@ definitionAnn = do
|
||||||
pure (name, ty, e)
|
pure (name, ty, e)
|
||||||
|
|
||||||
declaration :: Parser Declaration
|
declaration :: Parser Declaration
|
||||||
declaration = notFollowedBy (try let_) >> (declrec <|> decl)
|
declaration = notFollowedBy (try let_) >> do
|
||||||
where
|
keyword "let"
|
||||||
declrec = do
|
definitionAnn
|
||||||
try $ keyword "letrec"
|
|
||||||
(name, ty, expr) <- definitionAnn
|
|
||||||
pure (name, ty, LetRecP (name, expr) (Var name))
|
|
||||||
decl = do
|
|
||||||
keyword "let"
|
|
||||||
definitionAnn
|
|
||||||
|
|
||||||
-- | A program is a series of declarations and expressions to execute.
|
-- | A program is a series of declarations and expressions to execute.
|
||||||
type ProgramAST = [Declaration]
|
type ProgramAST = [Declaration]
|
||||||
|
|
|
@ -58,7 +58,6 @@ unparseAST = toStrict . toLazyText . snd . cata \case
|
||||||
let names' = fromLazyText (unwords $ map fromStrict $ toList names)
|
let names' = fromLazyText (unwords $ map fromStrict $ toList names)
|
||||||
in "λ" <> names' <> ". " <> unambiguous body
|
in "λ" <> names' <> ". " <> unambiguous body
|
||||||
LetFP defs body -> tag Block $ "let " <> unparseDefs defs <> " in " <> unambiguous body
|
LetFP defs body -> tag Block $ "let " <> unparseDefs defs <> " in " <> unambiguous body
|
||||||
LetRecFP def body -> tag Block $ "letrec " <> unparseDef def <> " in " <> unambiguous body
|
|
||||||
CtrF ctr e -> unparseCtr ctr e
|
CtrF ctr e -> unparseCtr ctr e
|
||||||
CaseF pats ->
|
CaseF pats ->
|
||||||
let pats' = fromLazyText $ intercalate "; " $ map (toLazyText . unparsePat) pats
|
let pats' = fromLazyText $ intercalate "; " $ map (toLazyText . unparsePat) pats
|
||||||
|
|
Loading…
Reference in New Issue