I have this AST data structure
data AST = Integer Int
| Let String AST AST
| Plus AST AST
| Minus AST AST
| Times AST AST
| Variable String
| Boolean Bool
| If AST AST AST
| Lambda String AST Type Type
| Application AST AST
| And AST AST
| Or AST AST
| Quot AST AST
| Rem AST AST
| Negate AST
| Eq AST AST
| Leq AST AST
| Geq AST AST
| Neq AST AST
| Lt AST AST
| Gt AST AST
and this evaluation code:
eval :: AST -> AST
eval = cata go where
go :: ASTF (AST) -> AST
go (LetF var e e') = eval $ substVar (var, e) e'
go (PlusF (Integer n) (Integer m)) = Integer (n + m)
go (MinusF (Integer n) (Integer m)) = Integer (n - m)
go (TimesF (Integer n) (Integer m)) = Integer (n * m)
go (QuotF (Integer n) (Integer m)) = Integer (quot n m)
go (RemF (Integer n) (Integer m)) = Integer (rem n m)
go (IfF (Boolean b) e e') = if b then e else e'
go (ApplicationF (Lambda var e _ _) e') = eval $ substVar (var, e') e
go (AndF (Boolean b) (Boolean b')) = Boolean (b && b')
go (OrF (Boolean b) (Boolean b')) = Boolean (b || b')
go (NegateF (Boolean b)) = Boolean (not b)
go (EqF e e') = Boolean (e == e')
go (NeqF e e') = Boolean (e /= e')
go (LeqF (Integer n) (Integer m)) = Boolean (n <= m)
go (GeqF (Integer n) (Integer m)) = Boolean (n >= m)
go (LtF (Integer n) (Integer m)) = Boolean (n < m)
go (GtF (Integer n) (Integer m)) = Boolean (n > m)
go astf = embed astf
I feel like there should be away to remove the explicit recursion for 'let' and application, but I'm not sure exactly which recursion scheme I should reach for. Which recursion scheme(s) can be used to remove recursion in this case and similar cases and do you have any good ways of identifying situations where said recursion scheme is applicable?
evalisn't directly expressible as a catamorphism becauseeval (Let x e e')appliesevaltosubst (x, eval e) e', which is not a subterm ofLet x e e'(eore'). Instead, consider the composition ofevaland substitution. If you generalize substitutionsubstto substitute many variables at once withsubsts, then you can get the following equation:where we do have a function of the form
(eval . substs _)applied to both subtermseande'. In order to account for the parameter ofsubsts, you can usecatawith an F-algebra where the carrier is a functionASTF (Sub -> AST) -> Sub -> AST, then you get to pass a different substitutionSubto each subterm.Another way to think of
evalSubstsis as an evaluator using an environment, mapping variables to values. Then to bind a variable, rather than to do a substitution, is just to insert its value in the environment, which gets looked up once you reach aVarnode.