Search code examples
haskellfunctional-programmingruntime-error

Haskell: Non-exhaustive patterns in stack


I have the following stack class:

newtype Stack my_stack = Stack [my_stack] deriving Show

getSize :: Stack my_stack -> Int 
getSize (Stack []) = 0
getSize (Stack (x:xs)) = 1 + getSize (Stack xs)

push :: my_stack -> Stack my_stack -> Stack my_stack
push x (Stack xs) = Stack (x:xs)

pop :: Stack my_stack -> Stack my_stack
pop (Stack (x:xs)) = Stack xs

getTop :: Stack my_stack -> my_stack
getTop (Stack (x:xs)) = x

however when I try to use the getTop function, I get the Non-exhaustive patterns in function getTop error. How can I declare the base case (which I assume is empty case?) for the getTop function so I won't be getting non-exhaustive pattern errors? Thank you!


Solution

  • Your function is unsafe right now. That warning is (correctly) notifying you that you don't handle the empty-stack case. Now, the question is: what do you want to do if the user calls getTop with an empty stack? You've got a few options. I'll sort them in roughly descending order of my preference.

    1. Return Maybe

    If we have a value that may or may not exist, the idiomatic approach is to return Maybe. Maybe is a special type that may or may not contain a value, and it's up to the caller to decide what to do with it. They might themselves opt to return a Maybe, or they might provide a default value, or they might just show the user an error. But the type system will enforce that they handle the situation.

    getTop :: Stack my_stack -> Maybe my_stack
    getTop (Stack []) = Nothing
    getTop (Stack (x:xs)) = Just x
    

    This is perfectly safe and gives the caller total control.

    2. Force the user to provide a default value

    Instead, you can require the user to specify the value they want if the stack is empty.

    getTop :: my_stack -> Stack my_stack -> my_stack
    getTop orelse (Stack []) = orelse
    getTop _ (Stack (x:xs)) = x
    

    This is still perfectly safe, like the previous solution, but in my opinion it's more awkward than an Option. The type signature Stack my_stack -> Maybe my_stack is very clearly indicative of what the function does: it takes a stack and might return a value from it. The type signature my_stack -> Stack my_stack -> my_stack is more nebulous: are we adding a value to the stack, are we returning a default, what are we doing? Further, if the user actually wants a Maybe, there may not be a suitable default value that's distinguishable from "nothing was on the stack".

    3. Throw an error

    This is not idiomatic Haskell and I do not recommend doing it in production. But if you're just writing some short code for personal practice, it can be useful for hacking.

    getTop :: Stack my_stack -> my_stack
    getTop (Stack []) = error "Empty stack in getTop"
    getTop (Stack (x:xs)) = x
    

    error is a special function that has signature String -> a (for any a). You provide it with an error message and it... well, it crashes your program. There are technically ways of catching an error, but they're awkward and have a lot of difficult quirks, so generally you should regard error as a hard crash and only use it if there's no possibility for recovery. Hence, I don't recommend it in this case, as "the stack was empty" is very much a recoverable condition.