-- | Untyped lambda calc for benchmarking
{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}
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)
import Control.DeepSeq.Generics (genericRnf)
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
instance NFData Term where rnf = genericRnf
-- | 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