'What does " Non type-variable argument in the constraint" really mean?
For example:
map (+1) 2
in ghci yields
<interactive>:23:1: error:
* Non type-variable argument in the constraint: Num [b]
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall b. (Num b, Num [b]) => [b]
I've seen many questions similar to mine, but all seem only to answer what we can deduce from this (that the type of the second argument to map
is wrong), and how to fix it - but not what error actually means . Where do things go wrong precisely?
Solution 1:[1]
The error arises during the type-deduction of your statement.
Since
(+1)
is of typeNum a => a -> a
2
is of typeNum a => a
map
is of type(a -> b) -> [a] -> [b]
We know that map (+1)
has to be of type (Num b) => [b] -> [b]
, and therefore map (+1) 2
of type (Num b, Num [b]) => [b]
. But [b]
is not just a type-variable, it's List of some type variable, where list is a data constructor. In an alternative version of Haskell where no syntactic sugar for lists exists, we might write (Num b, Num (List b))
.
This is a problem because by default, Haskell does not support non type-variable arguments for constraints. So the precise nature of the problem is not that Haskell doesn't know how to map over numbers - it's that it doesn't allow values of the type that our function call produces.
But that rule isn't strictly needed. By adding -XFlexibleContexts
when calling ghci, types of the sort that our method produces are now allowed. The reason for this is that the literal 2
in Haskell doesn't really represent a number - it represents an object of type Num a => a
, which is constructed from the Integral 2
using fromIntegral
. So the statement map (+1) 2
is equivalent to map (+1) (fromIntegral (2::Integer))
. This means that the literal "2" can represent anything, given the proper instantiation - including lists.
Solution 2:[2]
2
has the type Num a => a
; we haven't specified what a
is, except that it has to have a Num
instance.
map (+1)
has the type Num b => [b] -> [b]
; we have specified what b
is, except it has to have a Num
instance.
When we determine the type of map (+1) 2
, we are basically unifying Num a ~ Num b => [b]
.
2 :: Num a => a
map (+1) :: Num b => [b] -> [b]
map (+1) 2 :: (Num b, Num [b]) => [b]
And this is the problem. Num
requires a type variable like a
or b
, not a polymorphic type like [b]
, as its argument.
Solution 3:[3]
Here’s actual answer:
GHC tries to infer the types by itself, and failed, or in GHC lingo:
When checking the inferred type …
Basically the conflict is:
• GHC is happy to have any value as a parameter for map (+1)
, as long as it is compatible with its type. And map (+1)
wants a list of numbers, since +
wants two numbers and 1
already is one number, and map
always wants a list of values compatible with that.
• And here’s the kicker: GHC is happy to have 2
be any type! Because to save you the hassle of always having to specify if a literal 2
is of a certain type, GHC just treats numeric literals as Integer
and replaces it with fromInteger 2
. So since fromInteger
is from the class Num
, 2
can be anything that implemented that class Num
! So 2
could be a list too!
So GHC is stuck trying to fit those things together, and tells you:
“It needs to be some value, that is a number. … And where the list of those values is also a number!”
Or in Haskell:
it :: forall b. (Num b, Num [b]) => [b]
But there is no instance that makes lists numbers!
Or in GHC lingo:
Non type-variable argument in the constraint: Num [b]
That’s why you nowadays get this, yes definitely badly designed and confusing error message.
(Almost all GHC error messages are a cruel joke. The best thing you can do to understand them, is to read them bottom to top! And know all of Haskell’s type system freedoms and the GHC extensions that are forcing GHC to not make assumptions that would make these errors a much simpler sub-class of errors otherwise.)
Obviously, you’d normally just replace 2
with an actual list of some sort. But we’re not going to do that here. Since GHC wants crazy, GHC shall get crazy:
Silly resolution
So since it leaves us the option of making lists (of numbers) numbers, that’s what we shall do:
instance Num b => Num [b] where
(+) = zipWith (+)
(*) = zipWith (*)
abs = map abs
signum = map signum
fromInteger i = [fromInteger i]
-- or repeat [fromInteger i], if we’re evil. ;)
negate = map negate
So now, it works perfectly fine:
> map (+1) 2
[3]
Since 2
becomes fromInteger 2 :: Num a => [a]
, which puts 2
in a list ([2]
), and then map (+1)
is happy and turns it into [2+1]
. Which evaluates to [3]
.
This possibility is why the error message wasn’t a much simpler
“Error: 2
is not a list, but map
expects a list!”.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | chepner |
Solution 3 |