Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a default in a case statement in Haskell

Tags:

haskell

I am trying to create a new data declaration called place.

It looks like this:

data place = United States | France | England | Germany | Mexico | Canada

My hope is to then use a function called cap to take the place to its capital like so:

cap :: place -> String
cap a = case a of

            Spain           -> "Madrid"
            France          -> "Paris"
            England         -> "London"
            Germany         -> "Berlin"
            Mexico          -> "Mexico City"
            Canada          -> "Ottawa"
            _               -> undefined

However the last case, where I am attempting to catch all other entries that may not exist in the data declaration does not work. If I enter capital Wales for instance in GHCI, I don't get an undefined response. Instead I get a not in scope error. Can someone help me with my confusion and perhaps provide a legitimate way of trying to catch other cases?

like image 498
ZAX Avatar asked Feb 12 '13 23:02

ZAX


3 Answers

The problem isn't with how you handle missing cases - how you're doing it is fine. The problem is that the Wales constructor simply does not exist. So just like when you try to use a variable or function that has not been defined, you get a compilation error. Your cap function never even gets called, so no changes you could do to it, would affect this behaviour. There's nothing you can do to make code that uses non-existing constructors compile.

like image 136
sepp2k Avatar answered Nov 20 '22 11:11

sepp2k


When you enter capital Wales, there is no Wales in scope. It is not possible for you to construct a value that does not exist. If you have covered every possible case, then you do not need a default case.

like image 34
singpolyma Avatar answered Nov 20 '22 12:11

singpolyma


To riff on sepp2k's and singpolyma's answers, the point here is that Haskell's union types are exhaustive. When you define a union type with n cases, you are telling Haskell that those n cases are the only cases that exist for your type. As singpolyma points out, you've told Haskell that other cases don't even exist.

This has benefits and drawbacks. Exhaustiveness means that you and the compiler can guarantee that your functions are handling all possible inputs that they will be given. The drawback is that the set of cases is fixed at compilation time.

The simplest alternative here is two-part:

  1. Use an "open" type—one for which you can create arbitrarily many different instances at runtime—to represent nations and capitals. Strings are a good one here; there is an infinite number of different strings that you can construct at runtime. But you can also use a record type with a string member.
  2. Use key/value assignment data structures to represent the association between the countries and the capitals.

So you could represent nations and cities like this:

-- Since there are infinitely many different strings you could construct at runtime,
-- there are also infinitely many different Cities and Nations...
data City = City String deriving (Eq, Ord, Show)
data Nation = Nation String deriving (Eq, Ord, Show

The simplest key/value mapping type is [(k, v)], often known as an association list. It has of course an O(n) lookup time. A better one is to use Data.Map, which comes with the Haskell Platform.

like image 3
Luis Casillas Avatar answered Nov 20 '22 13:11

Luis Casillas