Let vs. Where
From HaskellWiki
(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]] | ||
[[Category:Syntax]] | [[Category:Syntax]] | ||
Revision as of 17:36, 13 October 2010
Haskell programmers often wonder whether to useThis 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 thatthat is, it can be written wherever expressions are allowed.
In contrast to that,like the pattern matching line of a function definition.
Contents |
1 Advantages of let
Consider you have the function
f :: s -> (a,s) f x = y where y = ... x ...
However, transforming to
f :: State s a f = State $ \x -> y where y = ... x ...
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
2 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
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)
3 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)
4 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 toreduces the performance dramatically.
You might see the reason better, if you rewrite this code usingCompare
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
thus it cannot be floated out.
In contrast to that, in the first case- Haskell-Cafe on Eta-expansion destroys memoization?
