Using Data.Align we can handle both the zipping and the length issues at once
matchWith :: (a -> b -> Bool) -> [a] -> [b] -> Bool
matchWith f as bs = and $ alignWith combiner as bs where
combiner = these (const False) (const False) f
This unfolds to the same code as your explicitly recursive function, but using tags from Data.These
to mark the various list alignments. It also generalizes to many other structures like Trees or Sequences if you generalize the and
.
matchWith :: (Foldable f, Align f) => (a -> b -> Bool) -> f a -> f b -> Bool
matchWith f as bs = Foldable.and $ alignWith combiner as bs where
combiner = these (const False) (const False) f
data Tree a = Tip | Branch a (Tree a) (Tree a) deriving ( Functor, Foldable )
instance Align Tree where
nil = Tip
align Tip Tip = Tip
align (Branch a la ra) Tip = Branch (This a) (fmap This la) (fmap This ra)
align Tip (Branch b lb rb) = Branch (That b) (fmap That lb) (fmap That rb)
align (Branch a la ra) (Branch b lb rb) =
Branch (These a b) (align la lb) (align ra rb)
So that we have
λ> matchWith (==) Tip Tip
True
λ> matchWith (==) (Branch 3 Tip Tip) (Branch 3 Tip Tip)
True
λ> matchWith (==) (Branch 3 Tip Tip) (Branch 3 Tip (Branch 3 Tip Tip))
False
(Might as well...)
instance Eq a => Eq (Tree a) where (==) = matchWith (==)