# Difference between revisions of "Let vs. Where"

(where and eta expansion) |
|||

Line 114: | Line 114: | ||

The auxiliary definition can either be a top-level binding, or included in f using <hask>let</hask> or <hask>where</hask>. |
The auxiliary definition can either be a top-level binding, or included in f using <hask>let</hask> or <hask>where</hask>. |
||

+ | |||

+ | == Problems with where == |
||

+ | |||

+ | If you run both |
||

+ | <haskell> |
||

+ | fib = (map fib' [0 ..] !!) |
||

+ | where |
||

+ | fib' 0 = 0 |
||

+ | fib' 1 = 1 |
||

+ | fib' n = fib (n - 1) + fib (n - 2) |
||

+ | </haskell> |
||

+ | and |
||

+ | <haskell> |
||

+ | fib x = map fib' [0 ..] !! x |
||

+ | where |
||

+ | fib' 0 = 0 |
||

+ | fib' 1 = 1 |
||

+ | fib' n = fib (n - 1) + fib (n - 2) |
||

+ | </haskell> |
||

+ | you will notice, that the second one runs considerably slower than the first one. |
||

+ | You may wonder, why just adding the explicit argument to <hask>fib</hask> (known as [[eta expansion]]) |
||

+ | reduces the performance dramatically. |
||

+ | |||

+ | You might see the reason better, if you rewrite this code using <hask>let</hask>. |
||

+ | Compare |
||

+ | <haskell> |
||

+ | fib = |
||

+ | let fib' 0 = 0 |
||

+ | fib' 1 = 1 |
||

+ | fib' n = fib (n - 1) + fib (n - 2) |
||

+ | in (map fib' [0 ..] !!) |
||

+ | </haskell> |
||

+ | and |
||

+ | <haskell> |
||

+ | fib x = |
||

+ | let fib' 0 = 0 |
||

+ | fib' 1 = 1 |
||

+ | fib' n = fib (n - 1) + fib (n - 2) |
||

+ | in map fib' [0 ..] !! x |
||

+ | </haskell> |
||

+ | . In the second case <hask>fib'</hask> is (re-)defined for every argument <hask>x</hask>, |
||

+ | thus it cannot be floated out. |
||

+ | In contrast to that, in the first case <hask>fib'</hask> can be moved to the top level by a compiler. |
||

+ | The <hask>where</hask> clause hid this structure |
||

+ | and made the application to <hask>x</hask> look like a plain eta expansion, which it is not. |
||

+ | |||

+ | * Haskell-Cafe on [http://www.haskell.org/pipermail/haskell-cafe/2010-October/084538.html Eta-expansion destroys memoization?] |
||

+ | |||

[[Category:Style]] |
[[Category:Style]] |

## Revision as of 17:36, 13 October 2010

Haskell programmers often wonder whether to use `let`

or `where`

.
This seems to be only a matter of taste in the sense of "Declaration vs. expression_style",
however there is more about it.

It is important to know that `let ... in ...`

is an expression,
that is, it can be written wherever expressions are allowed.
In contrast to that, `where`

is bound to a surrounding syntactic construct,
like the pattern matching line of a function definition.

## Advantages of let

Consider you have the function

```
f :: s -> (a,s)
f x = y
where y = ... x ...
```

and later you decide to put this into the `Control.Monad.State`

monad.
However, transforming to

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

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.

```
f :: s -> (a,s)
f x =
let y = ... x ...
in y
```

This is easily transformed to:

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

## Advantages of where

Because "where" blocks are bound to a syntactic construct, they can be used to share bindings between parts of a function that are not syntactically expressions. For example:

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

In expression style, you might use an explicit `case`

:

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

or a functional equivalent:

```
f x =
let a = w x
in select (f (h x a))
[(cond1 x, a),
(cond2 x, g a)]
```

or a series of if-then-else expressions:

```
f x
= let a = w x
in if cond1 x
then a
else if cond2 x
then g a
else f (h x a)
```

These alternatives are arguably less readable and hide the structure of the function more than simply using `where`

.

## Lambda Lifting

One other approach to consider is that let or where can often be implemented using lambda lifting and let floating, incurring at least the cost of introducing a new name. The above example:

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

could be implemented as:

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

The auxiliary definition can either be a top-level binding, or included in f using `let`

or `where`

.

## Problems with where

If you run both

```
fib = (map fib' [0 ..] !!)
where
fib' 0 = 0
fib' 1 = 1
fib' n = fib (n - 1) + fib (n - 2)
```

and

```
fib x = map fib' [0 ..] !! x
where
fib' 0 = 0
fib' 1 = 1
fib' n = fib (n - 1) + fib (n - 2)
```

you will notice, that the second one runs considerably slower than the first one.
You may wonder, why just adding the explicit argument to `fib`

(known as eta expansion)
reduces the performance dramatically.

You might see the reason better, if you rewrite this code using `let`

.
Compare

```
fib =
let fib' 0 = 0
fib' 1 = 1
fib' n = fib (n - 1) + fib (n - 2)
in (map fib' [0 ..] !!)
```

and

```
fib x =
let fib' 0 = 0
fib' 1 = 1
fib' n = fib (n - 1) + fib (n - 2)
in map fib' [0 ..] !! x
```

. In the second case `fib'`

is (re-)defined for every argument `x`

,
thus it cannot be floated out.
In contrast to that, in the first case `fib'`

can be moved to the top level by a compiler.
The `where`

clause hid this structure
and made the application to `x`

look like a plain eta expansion, which it is not.

- Haskell-Cafe on Eta-expansion destroys memoization?