GreenSaguaro
GreenSaguaro

Reputation: 3387

Do Not Understand fmap of Function and Value in Haskell

I am confused about the result of this expression:

fmap (*) 3

Which produces type:

fmap (*) 3 :: (Functor f, Num a, Num (f a)) => f (a -> a)

What I was expecting is that the result would be the same as doing:

(*) 3

Which produces a 1-parameter function:

Prelude> (*) 3 $ 10
30

I was expecting that fmap would apply 3 to (*) producing (*3), but the following produces an error:

Prelude> (fmap (*) 3) 10

<interactive>:201:1:
    Non type-variable argument in the constraint: Num (a -> a)
    (Use FlexibleContexts to permit this)
    When checking that `it' has the inferred type
      it :: forall a a1. (Num a, Num a1, Num (a1 -> a)) => a -> a

What type of parameter is the result of (fmap (*) 3) expecting?

Upvotes: 0

Views: 136

Answers (1)

ErikR
ErikR

Reputation: 52039

The key is here:

fmap (*) 3 :: (Functor f, Num a, Num (f a)) => f (a -> a)
                                 ^^^^^^^^^

In Haskell numeric literals are overloaded, so 3 could have any type b as long as b has a Num instance.

The compiler goes through the following analysis:

  1. 3 has some type b with constraint Num b
  2. fmap has type Functor f => (a -> c) -> f a -> f c
  3. So * has type a -> c and 3 has type f a
  4. * has type Num a => a -> a -> a so c = a -> a, and a has constraint Num a
  5. b = f a from (1) and (2)

Gathering up all of the constraints and equivalences:

fmap (*) 3 :: ( Functor f,       -- by (2)
                Num a,           -- by (4)
                Num (f a)        -- by (1) and (5)
              ) => f ( a -> a )  -- by (2) and c = a -> a

Update:

You asked for an example where fmap (*) 3 makes sense. What is problematic here is that (*) has arity 2 and fmap is usually used with a function of a single argument (arity 1). For instance:

fmap (+1) (Just 3)  = Just 4
fmap (+1) Nothing   = Nothing
fmap (*6) [1,2,3]   = [6,12,18]
fmap words getLine  = do x <- getLine; return (words x)

Note: (+1) is just the "add 1" function, e.g. \x -> x+1, and (*6) is just the multiply by 6 function, e.g. \x -> x*6.

So fmapping (*) :: Int -> Int -> Int is somewhat unusual. Of course, we can interpret the type of (*) to be Int -> (Int -> Int) which gives it arity 1, but then dealing with functors of functions is getting into a level of abstraction that we don't normally deal with.

Upvotes: 9

Related Questions