user3928256
user3928256

Reputation: 1003

Why do I need to define these two variables in my Haskell code?

-- file: ch03/BogusPattern.hs
data Fruit = Apple | Orange
    deriving (Show)

apple = "apple"
orange = "orange"

whichFruit :: String -> Fruit
whichFruit f = case f of
                "apple" -> Apple
                "orange" -> Orange

in this piece of code why do I need

apple = "apple"
orange = "orange"

?

Also I tried this:

-- file: ch03/BogusPattern.hs
data Fruit = Apple | Orange
    deriving (Show)

apple = "f1"
orange = "f2"

whichFruit :: String -> Fruit
whichFruit f = case f of
                "f1" -> Apple
                "f2" -> Orange

and it didn't work.

Ghci said the f1 and f2 are not in scope.

Shouldn't the whichFruit function try to match f into the two strings and return the fruit type base on the cases?

Thank you.

Upvotes: 1

Views: 209

Answers (2)

Rafael Almeida
Rafael Almeida

Reputation: 2397

You don't need apple and orange variables. whichFruit takes a string. In your first attempt, you can just call whichFruit "orange" and it will match the second case ("orange"). In your second example, you need to use whichFruit "f2" to accomplish the same thing.

You could also define x = "orange" and call whichFruit x to get an orange.

Upvotes: 5

Alexandre Lucchesi
Alexandre Lucchesi

Reputation: 725

Rafael's answer is totally right but I’d want to add a few points I found relevant.

Firstly, something more conceptual. It’s not strictly correct to call orange and banana variables. Rather, they’re constant functions or simply constants of type String. It’s so because you can’t actually change their values (hence, what we call immutability). Regarding your question about their need, I’m not sure about it, but probably the example’s idea was simply to define two different "fruits", so that one would be able to play with whichFruit in GHCi (or anywhere else) passing apple and orange instead of the string literals “apple” and “orange”.

That said, despite being a simple example illustrating very basic functionality, it’s worth mentioning that the way the function whichFruit is defined is not recommended (at all). This function only works for two different inputs: “apple” and “orange”. Anything other than these will cause whichFruit to fail unexpectedly. As whichFruit is defined only for a subset of its domain (String), it’s called a partial function. In other words, the type signature tells us whichFruit is a function that expects a String and returns a Fruit, but when it’s called with, say “banana”, it gives an error. Imagine someone imports your module (which defines whichFruit) and starts playing with it. While browsing the functions at GHCi, he finds out whichFruit and checks its type signature using :t whichFruit. It’s very likely that he’ll expect whichFruit to work with any string, but when he calls whichFruit “banana”:

*** Exception: BogusPattern.hs:(11,16)-(13,34): Non-exhaustive patterns in case

This is really bad, right? By facing that, one may think the problem is only related to being more explicit about what the function expects as arguments and attempt to just change the code to look like:

type FruitName = String

whichFruit :: FruitName -> Fruit
whichFruit f = case f of
            "apple" -> Apple
            "orange" -> Orange

Although using a type synonym certainly increases code readability, it definitely doesn't solve the problem, once nothing stops the caller from calling whichFruit passing something like "foo". Because of that, in Haskell, we strive to write functions which are total (defined for all inputs of its domain), leading to more robust code. By simply setting the flag -Wall in GHCi and loading the module, we can see that the compiler even warns us about that flaw:

λ: :set -Wall
λ: :l BogusPattern
[1 of 1] Compiling BogusPattern    ( BogusPattern.hs, interpreted )
...

BogusPattern.hs:11:16: Warning:
    Pattern match(es) are non-exhaustive
    In a case alternative:
        Patterns not matched:
            []
            (GHC.Types.C# #x) : _ with #x `notElem` ['a', 'o']
            [GHC.Types.C# 'a']
            (GHC.Types.C# 'a') : ((GHC.Types.C# #x) : _)
            with
            #x `notElem` ['p']
            ...
Ok, modules loaded: BogusPattern.

Ok... So how do we fix that? The simpler approach would be to add a default case:

whichFruit' :: FruitName -> Fruit
whichFruit' f = case f of
            "apple"  -> Apple
            _        -> Orange

Although it works, the result is probably undesirable (as we certainly don't want whichFruit' "banana" returning Orange). As an alternative solution, one could add a new value constructor to the Fruit data type in order to (mis)represent an invalid fruit and return that in the default case (_):

data Fruit = Apple | Orange | InvalidFruit
   deriving (Show)

This solution is not good for a number of reasons. Semantically, we expect value constructors to build values matching the type of the corresponding type constructor. In our case, we expect Apple and Orange to be Fruits (and that makes sense), but what is InvalidFruit supposed to mean? Also, it turns out that in Haskell we have much better ways of representing the possibility of failure, that is, in order to embed the notion of failure to whichFruit' we could simply rewrite it using, for instance, the Maybe type, as in:

whichFruit'' :: FruitName -> Maybe Fruit
whichFruit'' f = case f of
            "apple"  -> Just Apple
            "orange" -> Just Orange
            _        -> Nothing

This solution looks a lot better and, in my opinion, is the optimal one. The only drawback one may find is that it adds "extra overhead" and complicates a bit the caller function, which will have to handle a value with an added context of possible failure (Maybe Fruit) instead of just a value (Fruit). As a final note, I tell you that using such solution (or related ones, ie: Either String Fruit) is totally worth it and, as you get more experienced with Haskell, handling those "more complex" types becomes so natural that you won't even notice.

Upvotes: 0

Related Questions