1
votes

I am having difficulty understanding how to use pattern matching in guards.

I have this sample function, whose purpose is to return the last character in a string.

myFun :: [Char] -> Char
myFun str@(f:s:rst)
      | str == ""  = error "0 length string"
      | length str == 1 = head str
      | rst == "" = s
      | otherwise = lame (s:rst)

It is failing with "Non-exhaustive patterns in function" when passed a string with a single character.

I assume that Haskell realizes it can't use the form (f:s:rst) to match a single element list, and then fails prior to trying to evaluate the call to length.

How do I make a guard that will tell Haskell what to do when there is only a single element?

2

2 Answers

4
votes

You are pattern matching at the function definition level. The way you have described it, you are only covering the case where the string is at least two characters long:

myFun str@(f:s:rst)

You need to handle other cases as well. You can have a catch-all handler like this (needs to go as the last pattern):

myFun _ = ...

Or if you want to handle, for instance, the empty string, like this (prior to the catch-all):

myFun [] = ...

As to the purpose of your function, you are probably better off just using pattern matching and not using guards.

myFun :: [Char] -> Char
myFun [] = error "empty string"
myFun [x] = x
myFun (x:xs) = myFun xs

(Note that it would be more idiomatic to return a Maybe Char instead of crashing your program)

1
votes

Based on the particularly helpful answer from Chad Gilbert, and some additional tinkering, I have found a way to have my cake and eat it to. In case anyone has a similar stumbling block, here is a way to specify uncovered cases prior to declaring your guards:

myFun :: [Char] -> Char
myFun "" = ""
myFun str@(s:rst)
      | rst == "" = s
      | otherwise = myFun (s:rst)

This also works with multiple args :

strSplit :: [Char] -> [[Char]] -> [[Char]]
strSplit str [] = strSplit str [""]
strSplit "" _ = [""]
strSplit str@(s1:ns) list@(x:xs)
       | s1 == '|' = strSplit ns ("":list)
       | ns == "" = map reverse $ ((s1 : x) : xs)
       | otherwise  = strSplit ns ((s1 : x) : xs)

Or with stuff using the original pattern@(first:second:rest) idea:

    lastTwo :: [Char]->[Char]
    lastTwo "" = ""
    lastTwo [x] = [x]
    lastTwo str@(f:s:rst)
            | rst =="" = [f,s]
            | otherwise = lastTwo (s:rst)

This is probably super obvious to folks more familiar with Haskell, but I didn't realize that you were "allowed" to just declare the function multiple times using different syntax to cover different cases.