Reputation: 124
I'm really new to Haskell and functional programming in general, and I can't figure out why I keep getting "Non type-variable argument in the constraint: Num [a] (Use FlexibleContexts to permit this)" errors.
I have this function for looking up value in dictionary by key:
lookup1 x [] = []
lookup1 x (h:t) = if x == fst h then snd h else lookup1 x t
and it is working fine with char type, for example, lookup1 "b" [("a", "aa"), ("b", "bb"), ("c", "cc")]
returns "bb". But when I try the same with number type, for example lookup1 2 [(1, 11), (2, 22), (3, 33)]
I get this:
* Non type-variable argument in the constraint: Num [a]
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall a. Num [a] => [a]
Weird thing is that if I define the same function in ghci
it works with both chars and numbers.
As I also need function for returning all values from dictionary where dictionary can contain duplicate keys, I tried this workaround:
lookup_duplicates x [] = []
lookup_duplicates x (h:t) = if x == fst h then (snd h):(lookup_duplicates x t) else lookup_duplicates x t
lookup1 x (h:t) = let (a:b) = lookup_duplicates x (h:t) in a
which takes first value from lookup_duplicates
and it did work fine. But now I have this function that finds all values for given keys in dictionary:
find_all_values [] (h:t) = []
find_all_values (g:a) (h:t) = let found = (lookup1 g (h:t)) in (if found == [] then find_all_values a (h:t) else found:(find_all_values a (h:t)))
which also works very good with char types but with number types I get this:
* Non type-variable argument in the constraint: Num [a1]
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall a1. (Num [a1], Eq a1) => [[a1]]
Can anyone please guide me to the right direction?
Upvotes: 0
Views: 1407
Reputation: 531798
Your first problem is that you don't return a correct value from lookup1
when an item isn't found. You can't just return an empty list, because you aren't necessarily returning a list in the other cases (it depends on the type of value found in the tuples). Instead, you need to return a value of some Maybe
type:
lookup1 :: Eq a => a -> [(a, b)] -> Maybe b
lookup1 x [] = Nothing
lookup1 x ((k,v):t) | x == k = Just v
| otherwise = lookup1 x t
Alternatively, you could guarantee that list is returned, one containing zero or one values. This isn't ideal, though, since the type doesn't guarantee only an empty or singleton value is returned.
lookup1 :: Eq a => a -> [(a, b)] -> [b]
lookup1 x [] = []
lookup1 x ((k,v):t) | x == k = [v]
| otherwise = lookup1 x t
The reason your code worked for lookup1 "b" [("a", "aa"), ("b", "bb"), ("c", "cc")]
is that you were using String
values, and String
is just an alias for [Char]
; []
was a valid base case in that instance.
Upvotes: 1
Reputation: 29193
Look at
lookup1 x [] = []
lookup1 x (h:t) = if x == fst h then snd h else lookup1 x t
What's its type? The first equation says that we're taking two arguments, where the first is of unknown type, and the second is a list of unknown type, and we're returning another list of unknown type:
lookup1 :: _a -> [_b] -> [_c] -- Not fully solved
In the second equation, when you use fst
and snd
on the head of your second argument, you force that second argument to be a list of pairs
lookup1 :: _a -> [(_b, _d)] -> [_c]
and since your function can evaluate to snd h
, we know that _d
must be [_c]
.
lookup1 :: _a -> [(_b, [_c])] -> [_c]
and since you check (==)
between the first argument and fst h
, we know that _a ~ _b
and Eq _a
lookup1 :: Eq _a => _a -> [(_a, [_c])] -> [_c]
That's all the information we have, so we say we've totally solved this type and replace all the remaining variables with universally quantified types:
lookup1 :: Eq a => a -> [(a, [b])] -> [b]
and if you ask :type lookup1
in GHCi it'll tell you the same. This is wrong, because you can only ever use this to look up lists of things, when you should be able to look up whatever you want! Specifically, this function will either look up the target list, or, if it can't find it, evaluate to []
instead. Also, you say it works fine for Char
, and give the example
lookup1 "b" [("a", "aa"), ("b", "bb"), ("c", "cc")]
But that isn't Char
! That's [Char]
(aka String
)! String
s are zero or more characters enclosed in ""
; Char
s are exactly one character enclosed in ''
.
Also, if you tried
lookup1 'b' [('a', "a")]
you'd get ""
, and that's not really a sensible output, because you don't know if "b"
was found and just happened to point to ""
, or it wasn't there at all and we just defaulted.
When you try
lookup1 2 [(1, 11), (2, 22), (3, 33)]
each numeric literal starts with type Num _a => _a
(Haskell numeric literals are polymorphic). The type of lookup1
forces the second element of every pair to also be a list of something, so 11
, 22
, and 33
take on the type Num [a] => [a]
, which is not allowed without the FlexibleContexts
language extension (and is nonsense regardless).
The correct type for this is
lookup1 :: Eq a => a -> [(a, b)] -> Maybe b
lookup1 _ [] = _ -- Exercise for reader
lookup1 x (e:es) = _
Now, lookupDuplicates
(camel case is the convention) should have the type
lookupDuplicates :: Eq a => a -> [(a, b)] -> [b]
which is true for what you wrote, but now lookup1
is bad:
lookup1 x (h:t) = let (a:b) = lookupDuplicates x (h:t) in a
What happens if you give it an empty list? You will try to match it to h:t
, which will fail. Worse, you only take the list apart to put it back again. It should just be
lookup1 x es = head $ lookupDuplicates x es
-- Read: First result of looking up x in es
Yet, this is still undesirable. If you don't find your entry anywhere, you throw a runtime error (which you cannot catch unless you're in the IO
monad). You want to use another monad, probably Maybe
, for this:
lookup1 :: Eq a => a -> [(a, b)] -> Maybe b
lookup1 x es = case lookupDuplicates x es of
[] -> _ -- Exercise for reader
(e:_) -> _
The listsafe
library provides versions of list functions that work in Maybe
instead of being partial, so if you had that installed you could say
import qualified Data.List.Safe as LS
lookup1 x es = LS.head $ lookupDuplicates x es
Now, onto findAllValues
findAllValues [] (h:t) = []
findAllValues (g:a) (h:t) = let found = (lookup1 g (h:t))
in if found == []
then findAllValues a (h:t)
else found:(findAllValues a (h:t))
You can write this much more clearly:
import Data.Maybe
findAllValues :: Eq a => [a] -> [(a, b)] -> [b]
findAllValues keys dict = catMaybes $ flip lookup1 dict <$> keys
-- Go over the list of keys with (flip lookup1 dict), then remove the Maybes
-- (flip lookup1 dict) is a function that takes a key and looks it up in dict
-- (<$>) is the infix version of fmap which is the general version of map
-- catMaybes :: [Maybe a] -> [a]
-- Nothings stop existing and the Just wrappings evaporate
If you used the
import qualified Data.List.Safe as LS
lookup1 x es = LS.head $ lookupDuplicates x es
version above, then the most general type of lookup
is actually (MonadThrow m, Eq a) => a -> [(a, b)] -> m b
, which means findAllValues
simplifies to:
findAllValues keys dict = concat $ flip lookup1 dict <$> keys
because MonadThrow []
. (A failed lookup1
"magically" returns Nothing
in some cases and []
in others and Left (SomeException EmptyListException)
in yet others, all depending on type.)
Final note: always think of your type signatures first and write them down (at least for top level definitions). It really helps you get a handle on what you should do.
Upvotes: 3