0

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?

asked Aug 26, 2015 at 16:13
2
  • 6
    Why do you expect that fmap (*) 3 should be exactly the same as (*) 3? The fmap function is clearly making some kind of a difference, and i don't imagine you expect that, say, filter (*) 3 would be the same. Commented Aug 26, 2015 at 16:23
  • 1
    You're confusing mapping with application. fmap is a generalized map function, not at all like ($). Commented Aug 27, 2015 at 10:33

1 Answer 1

9

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.

answered Aug 26, 2015 at 16:39
Sign up to request clarification or add additional context in comments.

7 Comments

Can you give an example of (fmap (*) 3) X where X is a value that does not cause the expression to end in error?
@kurzweil4 It should be possible to do that, but why do you expect the result of fmap (*) 3 to be a function? It has type f (a -> a), which is not necessarily a function type. E.g., f could be [], making fmap (*) 3 a list of functions, not a function.
I do not know what to expect at all from the output, so seeing an example of how to use the output would help me to grasp the problem.
You can write a Num instance for lists, like instance Num a => [a] where fromInteger x = [fromInteger x], then fmap (*) 3 would be fmap (*) [3], which is [(*) 3].
@kurzweil4 - I added some more to my answer.
|

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.