1

I would like to check that homomorphism Applicative law holds for datatype BinTree:

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE AllowAmbiguousTypes #-} 
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}

module Laws where

import Control.Applicative ((<$>), liftA3)

import Data.Monoid

import Test.QuickCheck
import Test.QuickCheck.Function
import Test.QuickCheck.Gen

data BinTree a = Empty | Node a (BinTree a) (BinTree a) deriving (Show, Eq)

instance Functor BinTree where
    fmap _ Empty = Empty
    fmap f (Node x hi hd) = Node (f x) (fmap f hi) (fmap f hd)

instance Applicative BinTree where
    -- pure :: a -> BinTree a
    pure x = Node x (pure x) (pure x)

    -- <*> :: BinTree (a -> b) -> BinTree a -> BinTree b
    _ <*> Empty = Empty -- L1, 
    Empty <*> t = Empty
    (Node f l r) <*> (Node x l' r') = Node (f x) (l <*> l') (r <*> r')

instance (Arbitrary a) => Arbitrary (BinTree a) where
    arbitrary = oneof [return Empty, -- oneof :: [Gen a] -> Gen a
                liftA3 Node arbitrary arbitrary arbitrary]
                
-- Identity
apIdentityProp :: (Applicative f, Eq (f a)) => f a -> Bool
apIdentityProp v = (pure id <*> v) == v

apHomomorphismProp :: forall f a b. (Applicative f, Eq (f b)) => Fun a b -> a -> Bool
apHomomorphismProp (apply -> g) x = (pure @f g <*> pure x) == pure (g x)

main = quickCheck $ apHomomorphismProp @BinTree @Int @Int

However, when I execute the code, quickCheck applied to the applicative property returns:

(0 tests)

How can I solve this issue ?

F. Zer
  • 1,081
  • 7
  • 9

1 Answers1

2

Quite simply, your pure implementation generates an infinite tree. <*> preserves the infinite size of the trees on both of its sides. Then you compare the resulting infinite tree for equality with another infinite tree.

Well, it evidently doesn't find any discrepancy between them... but it doesn't terminate either. So QuickCheck never actually manages to confirm even one test case correct.

One way out could be to use not == but an equality operator which only checks for equality down to a limited depth, and assumes it'll be equal further down as well. (Note that it will still be exponentially expensive, so you can't even go to very great depth!)

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • Thank you, again +1. Do you know a suitable definition of `pure` for this datatype ? – F. Zer Jan 19 '23 at 00:11
  • @F.Zer `Node x Empty Empty`? – Mark Seemann Jan 19 '23 at 07:01
  • 1
    @F.Zer that's the wrong question to ask. Writing instance is not something you should do just for fun. Rather it's something you should do when you observe that such and such function that's natural for your data type matches such and such abstraction. The implementation you propose looks quite reasonable to me (it is analogous to [`ZipList`](https://hackage.haskell.org/package/base-4.17.0.0/docs/Control-Applicative.html#t:ZipList)), it's just not suitable for exact equality checks. – leftaroundabout Jan 19 '23 at 08:48
  • 1
    @MarkSeemann That would violate `(pure id <*> v) == v`. It's the same issue we have in `ZipList`, where `pure` must return an infinite list. – chi Jan 19 '23 at 08:50
  • @leftaroundabout I really appreciate your input. So, a better question to ask would be, how can I check homomorphism law holds for my instance ? – F. Zer Jan 19 '23 at 12:37
  • 1
    Yes, and I already mentioned one possibility. Another would be to compare not the trees directly, but instead choose an arbitrary _path_ down each tree and check that they're the same along that path. – leftaroundabout Jan 19 '23 at 12:52
  • Thank you for all you help. I've continue to check other laws. One question: could you tell me whether the custom equality operator is what you had in mind ? – F. Zer Jan 26 '23 at 22:52
  • ```instance Eq a => Eq (BinTree a) where t1 == t2 = eqTree 15 t1 t2 eqTree :: Eq a => Int -> BinTree a -> BinTree a -> Bool eqTree _ Empty Empty = True eqTree 0 (Node x _ _) (Node x' _ _ ) = x == x' eqTree n (Node x l r) (Node x' l' r') = x == x' && eqTree (n - 1) l l' && eqTree (n - 1) r r'``` – F. Zer Jan 26 '23 at 22:53
  • 1
    Basically yes, but don't make this an `Eq` instance, call it something like `≈≈` instead. – leftaroundabout Jan 26 '23 at 23:14
  • Thank you ! But then I can't reuse the properties, right ? I would have to re-write each of the laws: for this specific applicative, and use the `≈≈` for equality comparison. But I see the point about not making an `Eq` instance. Or...I could supply a custom equality operator to the properties. – F. Zer Jan 27 '23 at 13:30
  • You could also make a newtype wrapper for testing purposes that copies the applicative instance (with `-XGeneralizedNewtypeDeriving`) but uses the depth-limited `Eq` instance. – leftaroundabout Jan 27 '23 at 13:32
  • Thank you for your suggestion ! I'm getting there after some experimentation. I did: `newtype BinTree' a = BinTree a deriving Applicative instance Eq a => Eq (BinTree' a) where t1 == t2 = eqTree 15 t1 t2` – F. Zer Feb 04 '23 at 11:47
  • However, appears this error. Do you know how can I use `-XGeneralizedNewtypeDeriving` in this case ? Error: `Can't make a derived instance of ‘Applicative BinTree'’ (even with cunning GeneralizedNewtypeDeriving): cannot eta-reduce the representation type enough` – F. Zer Feb 04 '23 at 11:48
  • `newtype BinTree' a = BinTree' { getBinTree :: BinTree a } deriving (Functor, Applicative, Arbitrary)`. — The problem is, you forgot to give `BinTree'` a value constructor. (You also need to unwrap the value constructor in the `Eq` instance.) – leftaroundabout Feb 04 '23 at 11:54
  • `instance Eq a => Eq (BinTree' a) where t1 == t2 = eqTree 15 (getBinTree t1) (getBinTree t2)` – F. Zer Feb 04 '23 at 13:08
  • 1
    The value constructor is `BinTree'` right ? (On the RHS of the equals sign.) – F. Zer Feb 04 '23 at 13:09