-- | Untyped lambda calc for benchmarking {-# LANGUAGE DeriveGeneric, DeriveDataTypeable, CPP #-} module BenchLam where import Control.Applicative import Control.Monad (replicateM) import Data.List (foldl') import GHC.Generics (Generic) import Data.Typeable (Typeable) import Control.DeepSeq (NFData(..), deepseq) #if MIN_VERSION_deepseq (1,4,0) #else import Control.DeepSeq.Generics (genericRnf) #endif import Criterion (Benchmark, env, bench, nf) import Unbound.Generics.LocallyNameless type Var = Name Term data Term = V !Var | App !Term !Term | Lam !(Bind Var Term) deriving (Show, Generic, Typeable) instance Alpha Term #if MIN_VERSION_deepseq (1,4,0) instance NFData Term #else instance NFData Term where rnf = genericRnf #endif -- | lambda abstract over all the given vars lams :: [Var] -> Term -> Term lams [] = id lams (v:vs) = Lam . bind v . lams vs -- | apply the given term to the given terms apps :: Term -> [Term] -> Term apps = foldl' App -- eta-expand a term a given number of times etaN :: Fresh m => Term -> Int -> m Term etaN m n = do vs <- replicateM n (fresh $ s2n "v") let ms = map V vs return (lams vs $ apps m ms) -- | While the head is a lambda, descend under it and then do something; -- then close all the lambdas back up. workHeadUnderLams :: Fresh m => (Term -> m Term) -> (Term -> m Term) workHeadUnderLams comp = go where go m = case m of Lam bnd -> do (x, m') <- unbind bnd m'' <- go m' return $ Lam $ bind x m'' _ -> comp m freshNeutralTermHead :: (Applicative m, Fresh m) => Term -> m Term freshNeutralTermHead (App m n) = App <$> freshNeutralTermHead m <*> pure n freshNeutralTermHead (V v) = V <$> fresh v freshNeutralTermHead lam@(Lam {}) = return lam -- | A benchmark that creates an eta expansion of the term "x" of a given size -- and then freshens the "x" by traversing down below all the lambdas. -- -- Every time we go under a lambda, we freshen the body, so to go -- under N lambdas, we do O(N²) work. freshenEtaTermBench :: Int -> Benchmark freshenEtaTermBench n = let name = "freshen eta term of size " ++ show n in name `deepseq` env setup $ \m -> bench name $ nf (runFreshM . workHeadUnderLams freshNeutralTermHead) m where setup = return $ runFreshM $ etaN (V $ s2n "x") n