Search code examples
haskellsyntaxboilerplaterecursion-schemes

How to write less boilerplate in a expression evaluator written with recursion-schemes


With the recursion-scheme library it's easy to write abstract syntax trees and the corresponding expression evaluators:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveFunctor #-} 
{-# LANGUAGE DeriveFoldable #-} 
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE LambdaCase #-}

import Data.Functor.Foldable 
import Data.Functor.Foldable.TH

data Expr  = Plus Expr  Expr 
           | Mult Expr Expr 
           | Const Expr 
         deriving (Show, Eq)
makeBaseFunctor ''Expr  
-- Write a simple evaluator
eval :: Expr -> Int 
eval = cata alg 
  where 
    alg = \case
      PlusF  x y  -> (+) x y
      MultF  x y  -> (*) x y
      ConstF x    -> x 

Now look at the case in the alg function in the where clause of eval. I think all the x and y variables should not be necessary. Therefore I'm looking for some way (a syntax, a language extension etc.) to remove this boilerplate and just to write:

  PlusF  -> (+)
  MultF  -> (*)
  ConstF -> id 

Solution

  • Alternatively you could refactor your language to have binary operations on one hand and unary ones on the other. You'd write:

    data BinOp = PlusOp | MultOp deriving (Show, Eq)
    data UnOp  = ConstOp deriving (Show, Eq)
    
    data Expr  = Bin BinOp Expr  Expr
               | Un  UnOp  Expr
             deriving (Show, Eq)
    makeBaseFunctor ''Expr
    

    The evaluator then becomes:

    eval :: Expr -> Int
    eval = cata $ \case
      BinF op l r -> bin op l r
      UnF  op v   -> un op v
      where
        bin = \case
          PlusOp -> (+)
          MultOp -> (*)
    
        un = \case
          ConstOp -> id