3

I'm learning Haskell, from the book "Real World Haskell". In pages 66 and 67, they show the case expressions with this example:

fromMaybe defval wrapped =
    case wrapped of 
        Nothing    -> defval
        Just value -> value

I remember a similar thing in F#, but (as shown earlier in the book) Haskell can define functions as series of equations; while AFAIK, F Sharp cannot. So I tried to define this in such a way:

fromMaybe2 defval Nothing = defval
fromMaybe2 defval (Just value) = value

I loaded it in GHCi and after a couple of results, I convinced myself it was the same However; this makes me wonder, why should there be case expressions when equations:

  • are more comprehensible (it's Mathematics; why use case something of, who says that?);
  • are less verbose (2 vs 4 lines);
  • require much less structuring and syntatic sugar (-> could be an operator, look what they've done!);
  • only use variables when needed (in basic cases, such as this wrapped just takes up space).

What's good about case expressions? Do they exist only because similar FP-based languages (such as F#) have them? Am I missing something?

Edit:

I see from @freyrs's answer that the compiler makes these exactly the same. So, equations can always be turned into case expressions (as expected). My next question is the converse; can one go the opposite route of the compiler and use equations with let/where expressions to express any case expression?

JMCF125
  • 378
  • 3
  • 18
  • 1
    What your missing is if there is any computation to be done before your case statement. Otherwise, yes, using equations is probably preferred over case statements. – Farley Knight Dec 24 '13 at 18:15
  • 1
    @FarleyKnight, what if one uses `when`s and `let-in`s along with the equations? Can you give an example where one **must** use case expressions? – JMCF125 Dec 24 '13 at 18:17
  • 2
    No, honestly, I can't think of a "must have" situation, since you could always refactor into composition of two functions, one of them using the equations. A more senior Haskeller might find an example I can't think of, but nothing comes to mind for me. – Farley Knight Dec 24 '13 at 18:20
  • 1
    IIRC, pattern matching actually gets turned into a case statement in the intermediate language, so they're really the same thing, just a different syntax. Obviously case statements can be used as sub-expressions, which makes them more flexible, but pattern matching makes for more clean function definitions. – bheklilr Dec 24 '13 at 18:44

6 Answers6

4

The two functions compile into exactly the same internal code in Haskell ( called Core ) which you can dump out by passing the flags -ddump-simpl -dsuppress-all to ghc.

It may look a bit intimidating with the variable names, but it's effectively just a explicitly typed version of the code you wrote above. The only difference is the variables names.

fromMaybe2
fromMaybe2 =
  \ @ t_aBC defval_aB6 ds_dCK ->
    case ds_dCK of _ {
      Nothing -> (defval_aB6) defval_aB6;
      Just value_aB8 -> (value_aB8) value_aB8
    }

fromMaybe
fromMaybe =
  \ @ t_aBJ defval_aB3 wrapped_aB4 ->
    case wrapped_aB4 of _ {
      Nothing -> (defval_aB3) defval_aB3;
      Just value_aB5 -> (value_aB5) value_aB5
    }
Stephen Diehl
  • 8,271
  • 5
  • 38
  • 56
  • 1
    You say equations are turned into case expressions by the compiler. Then, my question is: is it always possible to go backwards, and turn case expressions into equations + `let`/`where` expressions? Also, +1, as you compiled the code and found that result I didn't expect. – JMCF125 Dec 24 '13 at 19:13
  • 1
    In principle yes. Top level pattern matching is just syntactic sugar for case statements. Also ``where`` expressions are transformed into ``let`` statements in Core as well. – Stephen Diehl Dec 24 '13 at 19:19
  • Good. Also that transformation makes sense, as `where`s are `let`s backwards. – JMCF125 Dec 24 '13 at 19:22
4

This comes from a culture of having small "kernel" expression-oriented languages. Haskell grows from Lisp's roots (i.e. lambda calculus and combinatory logic); it's basically Lisp plus syntax plus explicit data type definitions plus pattern matching minus mutation plus lazy evaluation (lazy evaluation was itself first described in Lisp AFAIK; i.e. in the 70-s).

Lisp-like languages are expression-oriented, i.e. everything is an expression, and a language's semantics is given as a set of reduction rules, turning more complex expressions into simpler ones, and ultimately into "values".

Equations are not expressions. Several equations could be somehow mashed into one expression; you'd have to introduce some syntax for that; case is that syntax.

Rich syntax of Haskell gets translated into smaller "core" language, that has case as one of its basic building blocks. case has to be a basic construct, because pattern-matching in Haskell is made to be such a basic, core feature of the language.


To your new question, yes you can, by introducing auxiliary functions as Luis Casillas shows in his answer, or with the use of pattern guards, so his example becomes:

foo x y | (Meh o p) <- z = baz y p o
        | (Gah t q) <- z = quux x t q
    where 
       z = bar x
Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 3
    @WillNess - it is more correct to say Haskell grows from Lambda Calculus roots (via the languages SASL and Miranda) than Lisp roots. In particular both Paul Hudak and Philip Wadler were rather critical of Lisp during the genesis of Haskell. References - Paul Hudak "Conception, Evolution and Application of Functional Programming Languages", Philip Wadler "Why calculating is better than Scheming". – stephen tetley Dec 24 '13 at 20:48
  • @stephentetley I meant it of course in the most general sense possible. Not *Common* Lisp, by a long long shot. :) Although, the [TR-19 by Friedman and Wise](http://www.cs.indiana.edu/cgi-bin/techreports/TRNNN.cgi?trnum=TR19), which foreshadows "listlessness" / "deforestation", and their TR-44 i.e. ["cons shouldn't"](http://www.cs.indiana.edu/cgi-bin/techreports/TRNNN.cgi?trnum=TR44), were both done on top Lisp 1.5-style, complete with `rplacd`s and the like IIRC. -- Will have to read the Wadler ref to see what he could possibly have meant there. For me, Scheme *is* lambda calculus. – Will Ness Dec 24 '13 at 22:50
  • @stephentetley also, for me SASL is Lisp too. :) I don't like macros, so, that might explain it. They are an ugly kludge, just like monads are. :) Haskell should've avoided the success for a bit longer, until true solutions like uniqueness/destructive update semantics/etc. were arrived at. For the life of me, I can't understand why [code like this](http://www.haskell.org/haskellwiki/Prime_numbers#Accumulating_Array) shouldn't be ***required*** 2b compiled w/destructive update. Monads belong ***under*** the hood. Users deserve someth' better (w/out any need 2 write "lift", ever, for one thing). – Will Ness Dec 24 '13 at 23:19
  • 1
    Will Ness, what don't you like about monads? What do you like about uniqueness/destructive update semantics (and what do you mean by these exactly)? Finally, how can you hate Scheme syntax-case macros? They are pretty amazing. – dfeuer Dec 25 '13 at 00:57
  • 1
    @dfeuer right, syntax-case is more a re-write system, and it's nice. I just like more declarative stuff. I just want to be able to write [this kind of code](http://www.haskell.org/haskellwiki/Prime_numbers#Accumulating_Array) instead of [this one](http://www.haskell.org/haskellwiki/Prime_numbers#Using_ST_Array). The use of that local var (an array, in the 1st snippet) is unique/single (cf"uniqueness types"...), so it should be updated destructively; and there should be some way for me to define what does that mean, 4any given type. It's just a vague idea. – Will Ness Dec 25 '13 at 09:22
  • @stephentetley Wadler's article indeed makes some strong points. :) One problem though is, pattern-matching and abstract data types. He says Miranda has facilities for abstract data types with laws. I'd like me some of that please. :) – Will Ness Dec 25 '13 at 12:49
3

The paper "A History of Haskell: Being Lazy with Class" (PDF) provides some useful perspective on this question. Section 4.4 ("Declaration style vs. expression style," p.13) is about this topic. The money quote:

[W]e engaged in furious debate about which style was “better.” An underlying assumption was that if possible there should be “just one way to do something,” so that, for example, having both let and where would be redundant and confusing. [...] In the end, we abandoned the underlying assumption, and provided full syntactic support for both styles.

Basically they couldn't agree on one so they threw both in. (Note that quote is explicitly about let and where, but they treat both that choice and the case vs. equations choice as two manifestations of the same basic choice—what they call "declaration style" vs. "expression style.")

In modern practice, the declaration style (your "series of equations") has become the more common one. case is often seen in this situation, where you need to match on a value that is computed from one of the arguments:

foo x y = case bar x of
            Meh o p -> baz y p o
            Gah t q -> quux x t q

You can always rewrite this to use an auxiliary function:

foo x y = go (bar x)
    where go (Meh o p) = baz y p o
          go (Gah t q) = quux x t q

This has the very minor disadvantage that you need to name your auxiliary function—but go is normally a perfectly fine name in this situation.

Luis Casillas
  • 29,802
  • 7
  • 49
  • 102
2

Case expression can be used anywhere an expression is expected, while equations can't. Example:

1 + (case even 9 of True -> 2; _ -> 3)

You can even nest case expression, and I've seen code that does that. However I tend to stay away from case expressions, and try to solve the problem with equations, even if I have to introduce a local function using where/let.

Pedro Rodrigues
  • 1,732
  • 11
  • 17
  • I have made a comment alike before here, but it's gone; so again: why should I use case expressions when, as you said, I can use equations with `where`s/`let`s? – JMCF125 Dec 25 '13 at 18:46
  • 1
    @JMCF125 I don't see any strong reason why you should use case expression. With a `where/let` you have to come up with a name for the local function; with the case you don't need to give it a name. Whether you go with the first or the second solution is purely a matter of taste, both are valid. I myself prefer the `where\let` solution. – Pedro Rodrigues Dec 25 '13 at 23:28
  • But I do see (and have listed) strong reasons for equations. I guess then I'll use equations with `let`s/`where`s as you. Thanks for the opinion. – JMCF125 Dec 26 '13 at 00:20
1

Every definition using equations is equivalent to one using case. For instance

negate True = False
negate False = True

stands for

negate x = case x of
             True -> False
             False -> True

That is to say, these are two ways of expressing the same thing, and the former is translated to the latter by GHC.

From the Haskell code that I've read, it seems canonical to use the first style wherever possible.

See section 4.4.3.1 of the Haskell '98 report.

Alexander Vieth
  • 876
  • 6
  • 9
1

The answer to your added question is yes, but it's pretty ugly.

case exp of
  pat1 -> e1
  pat2 -> e2
  etc.

can, I believe, be emulated by

let
  f pat1 = e1
  f pat2 = e2
  etc.
in f exp

as long as f is not free in exp, e1, e2, etc. But you shouldn't do that because it's horrible.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • Thanks to whoever suggested a correction. I don't know why it was rejected. – dfeuer Dec 24 '13 at 21:12
  • 1
    @dfeuer This answer as it stands is still wrong: `where` can be used to introduce local declarations to an equation, not to an expression. You should use `let` in this case: `let {f pat1 = e1; f pat2 = e2; etc} in f exp`. – Romildo Dec 25 '13 at 08:49
  • @Romildo, isn't `let {f pat1 = e1; f pat2 = e2} in f exp` the same as `f exp where {f pat1 = e1; f pat2 = e2}`? AFAIK, `where`s are just `let`s backwards. But again, I'm a beginner. – JMCF125 Dec 25 '13 at 18:23
  • @JMCF125, `where` is not just `let` backwards. `let in ` is a an expression, and as such it can be evaluated. Any variable bindings introducing ind `` are local to the let expression. ` = where ` is an equation that may define some variables appearing in ``. For that it may use some local definitions introduced by ``. Any variable binding introduced in `` is local to ``. – Romildo Dec 26 '13 at 02:39
  • Summary: `let` is an expression which may introduce local variable bindings, and `where` may introduce local variable bindings to the right hand side of an equation. – Romildo Dec 26 '13 at 02:39
  • @Romildo, not so, you used an equation with `where` in your example, but you could've done the same with let, as ` = let in `, *id est* ` = where `. Or, without equations: ` where `, the same as `let in `. – JMCF125 Dec 26 '13 at 18:45
  • @JMCF125, in ` = let in ` it just happens that the right hand side of the equation is a `let` expression. It could be any expression. The scope of any variable binding introduced by `decls` is limited to the `let` expression. With `where` the scope is the full equation, including any guards that may appear in the equation. – Romildo Dec 27 '13 at 02:06
  • 1
    @JMCF125, but ` where ` is not valid Haskell syntax. For instance, if typed in `GHCi`, the phrase `2*a where a=3` is rejected with the error message `parse error on input \`where'`. This happens because `where` can be used only with an equation, not with an expression. The source of misunderstanding may be the fact that the right hand side of an equation is an expression, making one to think the `where` is attached to it. But that is not the case. It is attached to the whole equation. – Romildo Dec 27 '13 at 02:08
  • @Romildo, GHCi is no good example, unless you're loading files. As I predicted, `let a = 3 in 2 * a` also gives an error. They both need to be inserted in an equation, because they do nothing on themselves: Haskell is purely functional, so one must construct declarations (equations), not stand-alone expressions. In GHCi, `putStrLn $ show 3` and `3` give the same result. Does that mean they're the same? No. In the same way, two differing results in GHCi don't need to be fundamentally different. – JMCF125 Dec 27 '13 at 11:48
  • @JMCF125, the expression `let a = 3 in 2 * a` evaluates to `6` with no problems in GHCi. It should not give any error. If it fails for you, something is wrong with your system. – Romildo Dec 28 '13 at 10:30
  • @JMCF125, GHCi accepts expressions for evaluation. It evaluates the expression and proceeds differently based on its type. If the type of the expression is `IO a`, that is, the expression is a computation in the `IO` monad, that computation is run and its return value is shown. Otherwise the value of the expression is shown. – Romildo Dec 28 '13 at 10:34
  • @JMCF125, being a purely functional language is not related to the need to construct declarations. GHCi accepts expressions, which are compiled and then evaluated. It also accepts `IO a` actions as if they were part of a `do` expression. In this case the action is run. A Haskell program is built from a set of modules. One of them should be named `Main`, and should export the variable `main :: IO a`. A module can contain only declarations (types, variables, ...). – Romildo Dec 28 '13 at 10:51
  • @JMCF125, as you think GHCi is no good example, then write a small Haskell program with an equation trying to use `where' in any place an expression is expected, except at the end of the equation, and you will get an error. – Romildo Dec 28 '13 at 10:52
  • @JMCF125, consider for instance the following equation in a Haskell program: `main = print (2*a where a = 3)`. It is wrong because `2*a where a = 3` is not a valid expression and the Haskell compiler just reports it as a syntax error. – Romildo Dec 28 '13 at 10:55
  • @JMCF125, but `main = print (2*a) where a = 3` is right, and the `where` belongs to the full equation, and not to any particular expression. The variable `a` is defined locally to the equation, and could be used on its right hand side, or even in any guards on the left hand side. – Romildo Dec 28 '13 at 10:59
  • You got me in those last two, unexpectedly for me, `main = print $ let a = 3 in 2 * a` compiled. So `let` is wider than `where`, because `where` is bound to the equation. What I thought wasn't that `where` wasn't bound, but that **`let` was bound** to the equation. My conclusion is that the behaviour of `where` should be expanded to the one of `let`. Thanks for your patience. – JMCF125 Dec 28 '13 at 12:01
  • @JMCF125, where can do things that let can't do, as Romildo noted. Specifically, it binds over guards, obviating a case expression. – dfeuer Dec 28 '13 at 14:00