Axel1999
Axel1999

Reputation: 21

Non-exhaustive patterns in a function [Haskell]

I have to make a function neighbours :: [((String, String), Int)] -> String -> [(String, Int)]

This is the function I came up with:

neighbours pairs@((x1,x2):xs) inputWord
 | pairs == [] = []
 | fst x1 == inputWord = ((snd x1), x2) : (neighbours xs inputWord)
 | snd x1 == inputWord = ((fst x1), x2) : (neighbours xs inputWord)
 | otherwise = (neighbours xs inputWord)

The output is supposed to be a list of tuples with the strings that were paired with inputWord in the tuple x1, together with the integer x2

The problem is that I get non-exhaustive patterns that I don't believe should be there.

I tried to replace pairs == [] = [] with xs == [] = []

This made the non-exhaustive patterns disappear for when the list was not empty, but it also prevented the function from traversing the last tuple of tuples.

Upvotes: 1

Views: 317

Answers (2)

chepner
chepner

Reputation: 532238

If you define a helper function to do the comparison to inputWord, the definition of neighbors becomes much simpler:

-- You can use other, more descriptive names instead.
type Foo = ((String, String), Int)
type Bar = (String, Int)

-- If it seems odd that I'm using [Bar] instead of Maybe Bar, see below
myLookup :: String -> Foo -> [Bar]
myLookup s ((s1, s2), i) | s == s1 = [(s2, i)]
                         | s == s2 = [(s1, i)]
                         | otherwise = []

neighbors :: [Foo] -> String -> [Bar]
neighbors [] _ = []
neighbors (x:xs) inputWord = case myLookup inputWord x of
                               []  ->     neighbors xs inputWord
                               [y] -> y : neighbors xs inputWord

It can be further simplified using concatMap, which simply concatenates the results of applying myLookup inputWord to each element of the input.

neighbors :: [Foo] -> String -> [Bar]
neighbors xs inputWord = concatMap (myLookup inputWord) xs

With this pattern in mind, we can switch to using the stricter Maybe Bar return type for myLookup by using mapMaybe from Data.Maybe instead of concatMap.

import Data.Maybe (mapMaybe)

type Foo = ((String, String), Int)
type Bar = (String, Int)

myLookup :: String -> Foo -> Maybe Bar
myLookup s ((s1, s2), i) | s == s1 = Just (s2, i)
                         | s == s2 = Just (s1, i)
                         | otherwise = Nothing

neighbors :: [Foo] -> String -> [Bar]
neighbors xs inputWord = mapMaybe (myLookup inputWord) xs

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477607

The only pattern you specify is:

neighbours pairs@((x1,x2):xs) inputWord

This thus means that this only works for non-empty lists. Indeed, the pattern ((x1, x2):xs) "fires" given it matches with a "cons" with (x1, x2) the first element of the list, and xs the rest of the elements.

The check pair == [] will never succeed, since the pattern already means that this can never be an empty list.

We can add a clause for the empty list pattern, for example as first pattern to match. Furthermore we can improve the readability of the code by using the (((x11, x12), x2):xs) pattern for a non-empty list. We then do not need to use fst and snd, but can use x11 and x12 directly:

neighbours :: Eq a => [((a, a), b)] -> a -> [(a, b)]
neighbours [] _ = []
neighbours pairs@(((x11, x12),x2):xs) inputWord
    | inputWord == x11 = (x11, x2) : tl
    | inputWord == x12 = (x12, x2) : tl
    | otherwise = tl
    where tl = neighbours xs inputWord

For example:

Prelude> neighbours [(('a', 'b'), 1), (('b', 'a'), 2), (('b', 'b'), 3), (('a', 'a'), 4)] 'a'
[('a',1),('a',2),('a',4)]

This function is however a bit "asymetrical" since for the two elements in the tuple, it "prefers" the first element over the second element. If the two items x11 and x12 are both equal to inputWord, it might make more sense to yield them both.

Upvotes: 2

Related Questions