packages feed

catamorphism-0.7.0.0: test/Data/Morphism/CataSpec.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- GHC 7.10 seems to require KindSignatures for the polymorph folds defined
-- below.
#if __GLASGOW_HASKELL__ >= 710
{-# LANGUAGE KindSignatures #-}
#endif

module Data.Morphism.CataSpec (main, spec) where

import Test.Hspec
import Test.QuickCheck
import Data.Morphism.Cata

import Data.Bool (bool)
import Data.Maybe (maybe)
import Data.Either (either)

data Unit = Unit
data Binary = Zero | One
data PolymorphSum a = PolymorphSum a
data PolymorphProduct a b = PolymorphProduct a b
data RegularRecursive a = Cons a (RegularRecursive a) | Empty
data RoseTree a = Node a [RoseTree a]

$(makeCata defaultOptions ''Unit)
$(makeCata defaultOptions ''Binary)
$(makeCata defaultOptions ''PolymorphSum)
$(makeCata defaultOptions ''PolymorphProduct)
$(makeCata defaultOptions ''RegularRecursive)
$(makeCata defaultOptions { cataName = "binaryFold" } ''Binary)
$(makeCata defaultOptions ''RoseTree)

$(makeCata defaultOptions { cataName = "bool'" } ''Bool)
$(makeCata defaultOptions { cataName = "maybe'" } ''Maybe)
$(makeCata defaultOptions { cataName = "either'" } ''Either)
$(makeCata defaultOptions { cataName = "foldr'" } ''[])

-- `main` is here so that this module can be run from GHCi on its own.  It is
-- not needed for automatic spec discovery.
main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  describe "type support" $ do
    it "handles Unit" $ do
      unit True Unit `shouldBe` True
      unit "foo" Unit `shouldBe` "foo"

    it "handles simple sum types" $ do
      binary 'z' 'o' Zero `shouldBe` 'z'
      binary 'z' 'o' One `shouldBe` 'o'

    it "handles polymorph sum types" $ do
      polymorphSum show (PolymorphSum True) `shouldBe` "True"
      polymorphSum length (PolymorphSum "Frerich") `shouldBe` 7

    it "handles polymorph product types" $ do
      let fn = (\b x -> show (if b then x + 1 else x - 1)) :: Bool -> Int -> String
      polymorphProduct fn (PolymorphProduct True 99) `shouldBe` "100"
      polymorphProduct fn (PolymorphProduct False 88) `shouldBe` "87"

    it "handles regular recursive types" $ do
      let length' = regularRecursive (\_ acc -> acc + 1) 0 :: RegularRecursive a -> Int
      length' Empty `shouldBe` 0
      length' (Cons () (Cons () (Cons () Empty))) `shouldBe` 3
      length' (Cons 'a' (Cons 'b' Empty)) `shouldBe` 2

    it "handles rose trees" $ do
      let treeSum = roseTree (\x xs -> sum (x:xs)) :: RoseTree Int -> Int
      treeSum (Node 3 []) `shouldBe` 3
      treeSum (Node 0 [Node 1 [Node 2 [], Node 3 []], Node 4 [Node 5 [], Node 6 []], Node 7 [Node 8 [], Node 9 []]]) `shouldBe` 45

  describe "custom options" $
    it "allows customizing the function name" $ do
      binaryFold 'z' 'o' Zero `shouldBe` 'z'
      binaryFold 'z' 'o' One `shouldBe` 'o'

  describe "equivalence" $ do
    let checkBinaryEquiv f g a b = property (\x -> f a b x == g a b x)

    it "can be used to define bool" $
      checkBinaryEquiv bool bool' "false" "true"

    it "can be used to define maybe" $
      checkBinaryEquiv maybe maybe' "<empty>" (++ "!!!")

    it "can be used to define either" $
      checkBinaryEquiv either either' (show :: Bool -> String) (++ "!!!")

    it "can be used to define foldr" $
      -- Well, we can get 'foldr', but flipped.
      checkBinaryEquiv foldr (flip foldr') (\(_ :: Int) (acc :: Int) -> acc + 1) 0