Reputation: 1003
-- 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
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
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 Fruit
s (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