Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A real life example when pattern matching is more preferable than a case expression in Haskell?

So I have been busy with the Real World Haskell book and I did the lastButOne exercise. I came up with 2 solutions, one with pattern matching

lastButOne :: [a] -> a
lastButOne ([]) = error "Empty List"
lastButOne (x:[]) = error "Only one element"
lastButOne (x:[x2]) = x
lastButOne (x:xs) = lastButOne xs

And one using a case expression

lastButOneCase :: [a] -> a
lastButOneCase x =
  case x of
    [] ->  error "Empty List"
    (x:[]) ->  error "Only One Element"
    (x:[x2]) ->  x
    (x:xs) ->  lastButOneCase xs

What I wanted to find out is when would pattern matching be preferred over case expressions and vice versa. This example was not good enough for me because it seems that while both of the functions work as intended, it did not lead me to choose one implementation over the other. So the choice "seems" preferential at first glance?

So are there good cases by means of source code, either in haskell's own source or github or somewhere else, where one is able to see when either method is preferred or not?

like image 360
Shiraaz.M Avatar asked Dec 03 '15 19:12

Shiraaz.M


People also ask

What is pattern matching in Haskell explain with the example?

In a functional language, pattern matching involves checking an argument against different forms. A simple example involves recursively defined operations on lists. I will use OCaml to explain pattern matching since it's my functional language of choice, but the concepts are the same in F# and Haskell, AFAIK.

How does pattern matching work in Haskell?

Pattern matching consists of specifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns. When defining functions, you can define separate function bodies for different patterns.


1 Answers

First a short terminology diversion: I would call both of these "pattern matching". I'm not sure there is a good term for distinguishing pattern-matching-via-case and pattern-matching-via-multiple-definition.

The technical distinction between the two is quite light indeed. You can verify this yourself by asking GHC to dump the core it generates for the two functions, using the -ddump-simpl flag. I tried this at a few different optimization levels, and in all cases the only differences in the Core were naming. (By the way, if anyone knows a good "semantic diff" program for Core -- which knows about at the very least alpha equivalence -- I'm very interested in hearing about it!)

There are a few small gotchas to watch out for, though. You might wonder whether the following is also equivalent:

{-# LANGUAGE LambdaCase #-}
lastButOne = \case
  [] ->  error "Empty List"
  (x:[]) ->  error "Only One Element"
  (x:[x2]) ->  x
  (x:xs) ->  lastButOneCase xs

In this case, the answer is yes. But consider this similar-looking one:

-- ambiguous type error
sort = \case
  [] -> []
  x:xs -> insert x (sort xs)

All of a sudden this is a typeclass-polymorphic CAF, and so on old GHCs this will trigger the monomorphism restriction and cause an error, whereas the superficially identical version with an explicit argument does not:

-- this is fine!
sort [] = []
sort (x:xs) = insert x (sort xs)

The other minor difference (which I forgot about -- thank you to Thomas DuBuisson for reminding me) is in the handling of where clauses. Since where clauses are attached to binding sites, they cannot be shared across multiple equations but can be shared across multiple cases. For example:

-- error; the where clause attaches to the second equation, so
-- empty is not in scope in the first equation
null [] = empty
null (x:xs) = nonempty
  where empty = True
        nonempty = False

-- ok; the where clause attaches to the equation, so both empty
-- and nonempty are in scope for the entire case expression
null x = case x of
  [] -> empty
  x:xs -> nonempty
  where
  empty = True
  nonempty = False

You might think this means you can do something with equations that you can't do with case expressions, namely, have different meanings for the same name in the two equations, like this:

null [] = answer where answer = True
null (x:xs) = answer where answer = False

However, since the patterns of case expressions are binding sites, this can be emulated in case expressions as well:

null x = case x of
  [] -> answer where answer = True
  x:xs -> answer where answer = False

Whether the where clause is attached to the case's pattern or to the equation depends on indentation, of course.

like image 191
Daniel Wagner Avatar answered Oct 15 '22 00:10

Daniel Wagner