I'm trying to understand some code and I'm getting myself tangled fairly well. Please help me to understand my logic, or lack thereof ...
To start:
*Main> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
If I just want f a
to be a function that takes one parameter, it's okay and makes sense:
*Main> :t \f -> fmap f (undefined :: String -> Int)
\f -> fmap f (undefined :: String -> Int) :: (Int -> b) -> String -> b
I can pass in a String
in the second param, which generates an Int
, and then use the function in the first param to generate the b
.
Now, I want f a
to be a function that takes two parameters, so I substitute that in:
*Main> :t \f -> fmap f (undefined :: String -> Int -> Bool)
\f -> fmap f (undefined :: String -> Int -> Bool)
:: ((Int -> Bool) -> b) -> String -> b
At this point, I'm confused. I already provided the function that converts from the String
and the Int
into the Bool
. How can I now provide another function that takes a Int -> Bool
to convert into a b
? Is this non-sensical or am I not reading this right?
Or maybe this is a case of a functor within a functor and more needs to be done to make this make sense? In which case, what?
There is actually no such thing as a function with two parameters in Haskell. Every function has exactly one parameter.
In particular, String -> Int -> Bool
is a function which accepts one String
parameter. (Of course, knowing that the result is again a function you are able to use it as if it were a function with two parameters.) So if you want to unify this with f a
, you need
f ~ (String->)
a ~ Int->Bool
Indeed Int->Bool
can itself be interpreted as a functor-application†
f ~ (String->)
g ~ (Int->)
b ~ Bool
so that String->Int->Bool ~ f (g b)
; thus
\f -> fmap (fmap f) (undefined :: String -> Int -> Bool)
:: (Bool -> b) -> String -> Int -> b
I don't think the function family of functors is really a good example for grasping properties of functors/applicatives/monads. List and maybes are generally much less confusing; instead of the plain function functor the equivalent Reader
is preferred when you need that functionality (pun not intended).
Regarding your original expression, that is actually not meaningless. If we translate it to a tamer functor, we could for instance write
> fmap ($2) [(>1), (>2), (>3)]
[True, False, False]
Much the same thing can be done with the function functor:
> fmap ($2) (<) 1
True
> fmap ($2) (<) 2
False
> fmap ($2) (<) 3
False
Of course that example is a bit too simple to be useful, but you can also implement nontrivial ones.
†Note that f
and g
are actually not the same functor. We tend to call them both “the function functor”, but really you get a different functor for every partial application of the (->)
constructor. That means, you can't in any way unify the two layers, even though there's a Monad (a->)
instance.