简体   繁体   中英

How do I fix ‘Eq a’ has kind ‘GHC.Prim.Constraint’ error in haskell?

I am writing a small function in Haskell to check if a list is a palindrome by comparing it with it's reverse.

checkPalindrome :: [Eq a] -> Bool
checkPalindrome l = (l == reverse l)
                    where
                        reverse :: [a] -> [a]
                        reverse xs
                            | null xs = []
                            | otherwise = (last xs) : reverse newxs
                                  where
                                      before = (length xs) - 1
                                      newxs = take before xs

I understand that I should use [Eq a] in the function definition because I use the equality operator later on, but I get this error when I compile:

Expected kind ‘*’, but ‘Eq a’ has kind ‘GHC.Prim.Constraint’
In the type signature for ‘checkPalindrome’:
  checkPalindrome :: [Eq a] -> Bool

Ps Feel free to correct me if I am doing something wrong with my indentation, I'm very new to the language.

Unless Haskell adopted a new syntax, your type signature should be:

checkPalindrome :: Eq a => [a] -> Bool

Declare the constraint on the left hand side of a fat-arrow, then use it on the right hand side.

Unlike OO languages, Haskell makes a quite fundamental distinction between

  • Constraints – typeclasses like Eq .
  • Types – concrete types like Bool or lists of some type.

In OO languages, both of these would be represented by classes , but a Haskell type class is completely different. You never have “values of class C ”, only “ types of class C ”. (These concrete types may then contain values, but the classes don't.)

This distinction may seem pedantic, but it's actually very useful. What you wrote, [Eq a] -> Bool , would supposedly mean: each element of the list must be comparable... but comparable to what ? You could have elements of different type in the list, how do you know that these elements are comparable to each other ? In Haskell, that's no issue, because whenever the function is used you first settle on one type a . This type must be in the Eq class. The list then must have all elements from the same type a . This way you ensure that each element of the list is comparable to all of the others, not just, like, comparable to itself . Hence the signature

checkPalindrome :: Eq a => [a] -> Bool

This is the usual distinction on the syntax level: constraints must always be written on the left of an => (implication arrow) .

The constraints before the => are “implicit arguments”: you don't explicitly “pass Eq a to the function” when you call it, instead you just pass the stuff after the => , ie in your example a list of some concrete type. The compiler will then look at the type and automatically look up its Eq typeclass instance (or raise a compile-time error if the type does not have such an instance). Hence,

GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
Prelude> let palin :: Eq a => [a] -> Bool; palin l = l==reverse l
Prelude> palin [1,2,3,2,1]
True
Prelude> palin [1,2,3,4,5]
False
Prelude> palin [sin, cos, tan]

<interactive>:5:1:
    No instance for (Eq (a0 -> a0))
      (maybe you haven't applied enough arguments to a function?)
      arising from a use of ‘palin’
    In the expression: palin [sin, cos, tan]
    In an equation for ‘it’: it = palin [sin, cos, tan]

...because functions can't be equality-compared.


Constraints may in OO also be interfaces / abstract base classes, which aren't “quite proper classes” but are still in many ways treated the same way as OO value-classes. Most modern OO languages now also support Haskell-style parametric polymorphism in addition to “element-wise”/covariant/existential polymorphism, but they require somewhat awkward extends trait-mechanisms because this was only implemented as an afterthought.

There are also functions which have “constraints in the arguments”, but that's a more advanced concept called rank- n polymorphism .

This is really an extended comment. Aside from your little type error, your function has another problem: it's extremely inefficient. The main problem is your definition of reverse .

reverse :: [a] -> [a]
reverse xs
  | null xs = []
  | otherwise = (last xs) : reverse newxs
      where
        before = (length xs) - 1
        newxs = take before xs

last is O(n), where n is the length of the list. length is also O(n), where n is the length of the list. And take is O(k), where k is the length of the result. So your reverse will end up taking O(n^2) time. One fix is to just use the standard reverse function instead of writing your own. Another is to build up the result recursively, accumulating the result as you go:

reverse :: [a] -> [a]
reverse xs0 = go [] xs0
  go acc [] = acc
  go acc (x : xs) = go (x : acc) xs

This version is O(n).

There's another source of inefficiency in your implementation:

checkPalindrome l = (l == reverse l)

This isn't nearly as bad, but let's look at what it does. Suppose we have the string "abcdefedcba" . Then we test whether "abcdefedcba" == "abcdefedcba" . By the time we've checked half the list, we already know the answer. So we'd like to stop there! There are several ways to accomplish this. The simplest efficient one is probably to calculate the length of the list as part of the process of reversing it so we know how much we'll need to check:

reverseCount :: [a] -> (Int, [a])
reverseCount xs0 = go 0 [] xs0 where
  go len acc [] = (len, acc)
  go len acc (x : xs) = len `seq`
                        go (len + 1) (x : acc) xs

Don't worry about the len `seq` bit too much; that's just a bit of defensive programming to make sure laziness doesn't make things inefficient; it's probably not even necessary if optimizations are enabled. Now you can write a version of == that only looks at the first n elements of the lists:

eqTo :: Eq a => Int -> [a] -> [a] -> Bool
eqTo 0 _ _ = True
eqTo _ [] [] = True
eqTo n (x : xs) (y : ys) =
  x == y && eqTo (n - 1) xs ys
eqTo _ _ _ = False

So now

isPalindrome xs = eqTo ((len + 1) `quot` 2) xs rev_xs
  where
    (len, rev_xs) = reverseCount xs

Here's another way, that's more efficient and arguably more elegant, but a bit tricky. We don't actually need to reverse the whole list; we only need to reverse half of it. This saves memory allocation. We can use a tortoise and hare trick:

splitReverse ::
  [a] ->
  ( [a] -- the first half, reversed
  , Maybe a -- the middle element
  , [a] ) -- the second half, in order
splitReverse xs0 = go [] xs0 xs0 where
  go front rear [] = (front, Nothing, rear)
  go front (r : rs) [_] = (front, Just r, rs)
  go front (r : rs) (_ : _ : xs) =
    go (r : front) rs xs

Now

isPalindrome xs = front == rear
  where
    (front, _, rear) = splitReverse xs

Now for some numbers, using the test case

somePalindrome :: [Int]
somePalindrome = [1..10000] ++ [10000,9999..1]

Your original implementation takes 7.523s (2.316 mutator; 5.204 GC) and allocates 11 gigabytes to build the test list and check if it's a palindrome. My counting implementation takes less than 0.01s and allocates 2.3 megabytes . My tortoise and hare implementation takes less than 0.01s and allocates 1.7 megabytes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM