What key intuition am I missing to understand this code? I do not understand how these compose. first expects a function (a->c) and constructed type (f a b). Yet, second yields (f a d). Furthermore, first and second are defined in terms of bimap which seems circular.
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
bimap g h = first g . second h
first :: (a -> c) -> f a b -> f c b
first g = bimap g id
second :: (b -> d) -> f a b -> f a d
second = bimap id
CodePudding user response:
second
is curried. You can write the implied parameter and it will be symmetrical with first
.
first g = bimap g id
second g = bimap id g
As for your question about whether the definitions are circular, the answer is yes. They are circular. According to a strict reading of the Haskell standard, the following implementation is correct
data F a b = F a b
instance Bifunctor F -- No functions implemented; take all three defaults
That will compile, according to the Haskell standard. But any attempt to call first
, second
, or bimap
will bottom out, since they'll just call each other forever.
However, if you look at the source code for Bifunctor
, there's another little annotation.
{-# MINIMAL bimap | first, second #-}
This is not an ordinary comment. This is a special instruction directed at GHC (the most popular Haskell compiler). GHC in particular supports minimal directives. This declares that an instance of Bifunctor
is only complete if it implements at least bimap
or both first
and second
. So on GHC, if you fail to satisfy that condition in your own instance
, you'll get a compiler error. This is also reflected in the Hackage docs
Minimal complete definition
bimap | first, second
The expectation is that you, as the programmer, override some of these defaults, and the remaining defaults can just silently fall into place.
You can see the same behavior more directly in the built-in typeclass Eq
class Eq a where
(==), (/=) :: a -> a -> Bool
{-# INLINE (/=) #-}
{-# INLINE (==) #-}
x /= y = not (x == y)
x == y = not (x /= y)
{-# MINIMAL (==) | (/=) #-}
Both (==)
and (/=)
are implemented in terms of each other, and there's a MINIMAL
directive at the bottom indicating that any instance must implement at least one of the two functions.