132
votes

I am new to Haskell and I am very confused by Where vs. Let. They both seem to provide a similar purpose. I have read a few comparisons between Where vs. Let but I am having trouble discerning when to use each. Could someone please provide some context or perhaps a few examples that demonstrate when to use one over the other?

Where vs. Let

A where clause can only be defined at the level of a function definition. Usually, that is identical to the scope of let definition. The only difference is when guards are being used. The scope of the where clause extends over all guards. In contrast, the scope of a let expression is only the current function clause and guard, if any.

Haskell Cheat Sheet

The Haskell Wiki is very detailed and provides various cases but it uses hypothetical examples. I find its explanations too brief for a beginner.

Advantages of Let:

f :: State s a
f = State $ \x -> y
   where y = ... x ...

Control.Monad.State

will not work, because where refers to the pattern matching f =, where no x is in scope. In contrast, if you had started with let, then you wouldn't have trouble.

Haskell Wiki on Advantages of Let

f :: State s a
f = State $ \x ->
   let y = ... x ...
   in  y

Advantages of Where:

f x
  | cond1 x   = a
  | cond2 x   = g a
  | otherwise = f (h x a)
  where
    a = w x

f x
  = let a = w x
    in case () of
        _ | cond1 x   = a
          | cond2 x   = g a
          | otherwise = f (h x a)

Declaration vs. Expression

The Haskell wiki mentions that the Where clause is declarative while the Let expression is expressive. Aside from style how do they perform differently?

Declaration style                     | Expression-style
--------------------------------------+---------------------------------------------
where clause                          | let expression
arguments LHS:     f x = x*x          | Lambda abstraction: f = \x -> x*x
Pattern matching:  f [] = 0           | case expression:    f xs = case xs of [] -> 0
Guards:            f [x] | x>0 = 'a'  | if expression:      f [x] = if x>0 then 'a' else ...
  1. In the first example why is the Let in scope but Where is not?
  2. Is it possible to apply Where to the first example?
  3. Can some apply this to real examples where the variables represent actual expressions?
  4. Is there a general rule of thumb to follow when to use each?

Update

For those that come by this thread later on I found the best explanation to be found here: "A Gentle Introduction to Haskell".

Let Expressions.

Haskell's let expressions are useful whenever a nested set of bindings is required. As a simple example, consider:

let y   = a*b
    f x = (x+y)/y
in f c + f d

The set of bindings created by a let expression is mutually recursive, and pattern bindings are treated as lazy patterns (i.e. they carry an implicit ~). The only kind of declarations permitted are type signatures, function bindings, and pattern bindings.

Where Clauses.

Sometimes it is convenient to scope bindings over several guarded equations, which requires a where clause:

f x y  |  y>z           =  ...
       |  y==z          =  ...
       |  y<z           =  ...
     where z = x*x

Note that this cannot be done with a let expression, which only scopes over the expression which it encloses. A where clause is only allowed at the top level of a set of equations or case expression. The same properties and constraints on bindings in let expressions apply to those in where clauses. These two forms of nested scope seem very similar, but remember that a let expression is an expression, whereas a where clause is not -- it is part of the syntax of function declarations and case expressions.

5
I was puzzled by the difference between let and where when I first started to learn Haskell. I think the best way to understand it is to realize that there's very little difference between the two, and therefore nothing to worry about. The meaning of where given in terms of let through a very simple mechanical transformation. See haskell.org/onlinereport/decls.html#sect4.4.3.2 This transformation exists for notational convenience only, really.Tom Ellis
I typically use one or the other depending on what I want to define first. For example, people often use functions, and then define them in where. Let is used if one wants a sort of imperative looking function.PyRulez
@Tom Ellis, Tom, I was trying to understand the link you refer to but it was too difficult for me, could you explain this simple transformation to mere mortals?jhegedus
@jhegedus: f = body where x = xbody; y = ybody ... means f = let x = xbody; y = ybody ... in bodyTom Ellis
Thank you Tom ! Can it go the other way around ? Is it possible to transform a let expression into a case .... of ... where expression somehow ? I am not sure about this.jhegedus

5 Answers

44
votes

1: The problem in the example

f :: State s a
f = State $ \x -> y
    where y = ... x ...

is the parameter x. Things in the where clause can refer only to the parameters of the function f (there are none) and things in outer scopes.

2: To use a where in the first example, you can introduce a second named function that takes the x as a parameter, like this:

f = State f'
f' x = y
    where y = ... x ...

or like this:

f = State f'
    where
    f' x = y
        where y = ... x ...

3: Here is a complete example without the ...'s:

module StateExample where

data State a s = State (s -> (a, s))

f1 :: State Int (Int, Int)
f1 = State $ \state@(a, b) ->
    let
        hypot = a^2 + b^2
        result = (hypot, state)
    in result

f2 :: State Int (Int, Int)
f2 = State f
    where
    f state@(a, b) = result
        where
        hypot = a^2 + b^2
        result = (hypot, state)

4: When to use let or where is a matter of taste. I use let to emphasize a computation (by moving it to the front) and where to emphasize the program flow (by moving the computation to the back).

30
votes

While there is the technical difference with respect to guards that ephemient pointed out, there is also a conceptual difference in whether you want to put the main formula upfront with extra variables defined below (where) or whether you want to define everything upfront and put the formula below (let). Each style has a different emphasis and you see both used in math papers, textbooks, etc. Generally, variables that are sufficiently unintuitive that the formula doesn't make sense without them should be defined above; variables that are intuitive due to context or their names should be defined below. For example, in ephemient's hasVowel example, the meaning of vowels is obvious and so it need not be defined above its usage (disregarding the fact that let wouldn't work due to the guard).

14
votes

Legal:

main = print (1 + (let i = 10 in 2 * i + 1))

Not legal:

main = print (1 + (2 * i + 1 where i = 10))

Legal:

hasVowel [] = False
hasVowel (x:xs)
  | x `elem` vowels = True
  | otherwise = False
  where vowels = "AEIOUaeiou"

Not legal: (unlike ML)

let vowels = "AEIOUaeiou"
in hasVowel = ...
7
votes

I found this example from LYHFGG helpful:

ghci> 4 * (let a = 9 in a + 1) + 2  
42  

let is an expression so you can put a let anywhere(!) where expressions can go.

In other words, in the example above it is not possible to use where to simply replace let (without perhaps using some more verbose case expression combined with where).

5
votes

Sadly, most of the answers here are too technical for a beginner.

LHYFGG has a relevant chapter on it -which you should read if you haven't already, but in essence:

  • where is just a syntactic construct (not a sugar) that are useful only at function definitions.
  • let ... in is an expression itself, thus you can use them wherever you can put an expression. Being an expression itself, it cannot be used for binding things for guards.

Lastly, you can use let in list comprehensions too:

calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]
-- w: width
-- h: height

We include a let inside a list comprehension much like we would a predicate, only it doesn't filter the list, it only binds to names. The names defined in a let inside a list comprehension are visible to the output function (the part before the |) and all predicates and sections that come after of the binding. So we could make our function return only the BMIs of people >= 25: