packages feed

free-foil-0.6.0: test/Control/Monad/Free/Foil/HoistSpec.hs

{-# LANGUAGE DataKinds      #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
-- | 'foldAST' and 'hoistAST' on the untyped λ-calculus of
-- "Control.Monad.Free.Foil.Example". The identity transformations are
-- identities, a round trip through a sum of signatures is one, and a fold
-- into a node count counts.
module Control.Monad.Free.Foil.HoistSpec (spec) where

import           Data.Bifunctor.Sum              (Sum (..))
import           Test.Hspec

import           Control.Monad.Foil              (NameBinder, S (..))
import           Control.Monad.Free.Foil
import           Control.Monad.Free.Foil.Example

-- | The terms under test: Church numerals and their applications.
terms :: [(String, Expr VoidS)]
terms =
  [ ("church " ++ show n, churchN n) | n <- [0 .. 6 :: Int] ] ++
  [ ("church 2 applied to church 3", AppE (churchN 2) (churchN 3))
  , ("church 3 normalised", nf' (AppE (churchN 2) (churchN 3)))
  ]

-- | A node count, indexed by the scope so that it can be the carrier of
-- 'foldAST'.
newtype Count (n :: S) = Count { getCount :: Int }

-- | The number of variables and nodes of a term, through 'foldAST'.
sizeOf :: Expr n -> Int
sizeOf = getCount . foldAST (const (Count 1)) (\_ body -> Count (getCount body)) node
  where
    node :: ExprF (Count m) (Count m) -> Count m
    node = \case
      AppF f x -> Count (1 + getCount f + getCount x)
      LamF b   -> Count (1 + getCount b)

spec :: Spec
spec = do
  describe "hoistAST" $ do
    it "is the identity on the identity transformation" $
      mapM_ (\(name, t) -> (name, show (hoistAST id t)) `shouldBe` (name, show t)) terms
    it "round-trips through a sum of signatures" $
      let there = hoistAST L2 :: Expr n -> AST NameBinder (Sum ExprF ExprF) n
          back  = hoistAST (\case L2 x -> x; R2 x -> x)
      in mapM_ (\(name, t) -> (name, show (back (there t))) `shouldBe` (name, show t)) terms
  describe "foldAST" $ do
    it "rebuilds a term with the constructors" $
      mapM_ (\(name, t) -> (name, show (foldAST Var ScopedAST Node t)) `shouldBe` (name, show t)) terms
    it "counts the nodes of a Church numeral" $
      mapM_ (\n -> (n, sizeOf (churchN n)) `shouldBe` (n, 2 * n + 3)) [0 .. 6 :: Int]