Haskell List Comprehension Non-exhaustive pattern when calling more than one parameter

346 Views Asked by At

To start with I have created a Type StudentMark which is a tuple taking firstly a String and secondly an Int.

type StudentMark = (String, Int)

This is my capMarks function:

capMarks :: [StudentMark] -> [StudentMark]
capMarks [cMarks] = [(st, mk) | (st, mk) <- [capMark cMarks]]

And here is my capMark function:

capMark :: StudentMark -> StudentMark
capMark (st, mk)
    |   mk > 39   =   (st, 40)
    |   mk < 40   =   (st, mk)

It is supposed to return:

[("Jo", 37), ("Sam", 40)]

from:

capMarks [("Jo", 37), ("Sam", 76)]

But will only return the correct and expected response when I input just 1 parameter into the function, for example:

capMarks [("Jake", 50)]

Or

capMarks [("Jake"), 30]

But using two (or more) as it's supposed to will just tell me there is a Non-exhaustive pattern in the capMarks function.

2

There are 2 best solutions below

4
On BEST ANSWER

Let's analyze your capMarks function:

capMarks :: [StudentMark] -> [StudentMark]
capMarks [cMarks] = [(st, mk) | (st, mk) <- [capMark cMarks]]

First of all capMarks [cMarks] = ... is a pattern matching. This matches a list that contains a single element. I assume that you want to do something with an entire list, so change this to capMarks cMarks = ...

Next ... [(st, mk) | (st, mk) <- [capMark cMarks]] will apply the capMark function to the only element in your original pattern matching scheme and then put the result as the only element of a list. It appears that you want to apply capMark to each element of a list. So if we follow the previous suggestion, you need to do something like ... [capMark mark | mark <- cMarks]. This does exactly as stated earlier: apply capMark to each element of the cMarks list.

Final version:

capMarks :: [StudentMark] -> [StudentMark]
capMarks cMarks = [capMark mark | mark <- cMarks]

Alternatively, you can also use pattern matching and explicit recursion:

capMarks [] = []  
capMarks (x:xs) = capMark x : capMarks xs

The first line says that capMarks applied to an empty list is an empty list. The second line says that capMarks applies to a list with at least one element will apply capMark to the first element and then recursively apply capMarks to the rest of the list.

This is such a common pattern in Haskell that there is a function called map that generalizes it. Using map is incredibly simple:

capMarks cMarks = map capMark cMarks

map has type (a -> b) -> [a] -> [b] which means it takes a function and a list and returns a list. (The a and b just tell the compiler which types have to be the same.) map then applies the function to each element in the input list.

Eventually you will learn about partial function application and point-free style. With these two concepts, the version using map can be simplified slightly:

capMarks = map capMark

Don't worry too much about this yet. I'm just adding it here for completeness.

6
On

You should check how pattern matching works in Haskell.

capMarks [x] will only match a list with one element. What you probably want is something like capMarks myList = [ ... | ... <- f myList] or define the rests of the cases in a recursive manner.

For example

capMarks [] = []
capMarks x:xs = capMark x : capMarks xs

This simplified "version" works in hugs

capMarks :: [Integer] -> [Integer]
capMarks xs = [(*) 2 x | x <- xs]