

How is lazy evaluation in Haskell useful? - Garbage
http://arstechnica.com/information-technology/2012/11/how-is-lazy-evaluation-in-haskell-useful/

======
danieldk
Note that there is a elephant in the room that the answers fail to reflect on.
The questioner asks:

 _How can this paradigm be used to build predictable software that works as
intended when we have no guarantee when and where an expression will be
evaluated?_

While it is true that this will not lead to unexpected computations in a pure
language, it may lead to excessive memory use. For instance, consider the
definition of Data.Map.Lazy.Map:

[http://www.haskell.org/ghc/docs/latest/html/libraries/contai...](http://www.haskell.org/ghc/docs/latest/html/libraries/containers/src/Data-
Map-Base.html#Map)

The Map data type is spine-strict and key strict in Weak Head Normal Form
(WHNF). However, it is not strict in its values, and values may be thunks
(unevaluated expressions). This may result the memory usage of the values to
be much larger than when they are fully evaluated.

There is now also a variant of Data.Map that is value-strict, but only in
WHNF, so only the outermost constructors may have been evaluated, and you'd
have to evaluate the data further to have thunks replaced by actual values.

Thunk leaks are something that every new Haskell programmer will bump into and
is something that makes programs less predictable if you are not yet
comfortably reason about strictness and laziness.

Edward Yang wrote a nice explanation of such leaks:

<http://blog.ezyang.com/2011/05/anatomy-of-a-thunk-leak/>

I very much enjoy Haskell, but I think that the benefits of laziness are much
overstated. Strict languages are often easier to reason about and laziness can
often be emulated when necessary. That said, Haskell has many other
attractions ;).

~~~
allertonm
I guess the benefit of lazy by default is that (given Haskell is an experiment
in purity) it sets the expectation that things will be side-effect free by
default.

Were that not a design criteria it might have been better to make things
strict by default and have a "lazy" monad rather than do lazy by default and
use monads for all the side effecting stuff.

~~~
ky3
Total functions embed into partial ones, which is why we have a partiality
monad. The opposite arrow doesn't exist, which is why we _don't_ have a
totality monad.

Now lazy and eager are properly adjectives that govern beta reduction, that
is, the _interaction_ between a function and its argument.

The short of it is that neither an eager nor a lazy monad makes any sense.

(Heh, I'm totally citing this in the monads class I teach as proof of why
unpacking monads starting from functors is a win.)

~~~
allertonm
Happy to be of service with my naive wonderings :) Anyway, thanks, I'm not
much of a PLT guy but I can begin to see why this could not work.

------
gtani
Let me point out that the haskell / GHC toolset is broad and deep and
constantly being expanded, and also the most common causes of space leaks are
becoming documented (the wiki and many stackoverflow threads. Tools:

ghc-vis, to visualize sharing: <http://felsin9.de/nnis/ghc-vis/>

\--------------

profilers and analysis of space/time behavior of data structures

[http://www.haskell.org/ghc/docs/latest/html/users_guide/prof...](http://www.haskell.org/ghc/docs/latest/html/users_guide/prof-
heap.html)

<http://blog.ezyang.com/2011/05/space-leak-zoo/>

[http://lambdamaphone.blogspot.com/2011/02/automatic-
asymptot...](http://lambdamaphone.blogspot.com/2011/02/automatic-asymptotic-
analysis.html)

\--------------

and, sort of a stacktrace:

[http://jpmoresmau.blogspot.com/2012/10/using-ghci-history-
in...](http://jpmoresmau.blogspot.com/2012/10/using-ghci-history-in-eclipsefp-
debugger.html)

[https://plus.google.com/u/0/107890464054636586545/posts/CfKE...](https://plus.google.com/u/0/107890464054636586545/posts/CfKEHZuqviy)

<http://hackage.haskell.org/trac/ghc/wiki/Status/May12>

\-----------

(not related to time/space reasoning, but a common pea under the mattress): in
7.4 you don't need top level "let" in GHCi

[http://www.haskell.org/ghc/docs/7.4.1/html/users_guide/inter...](http://www.haskell.org/ghc/docs/7.4.1/html/users_guide/interactive-
evaluation.html#ghci-decls)

------
Tyr42
Might as well read the programmers.stackexchange post [1], it's better
formatted.

[1] [http://programmers.stackexchange.com/questions/163985/why-
is...](http://programmers.stackexchange.com/questions/163985/why-is-the-
concept-of-lazy-evaluation-useful)

~~~
Evbn
And Daniel Wagner links to a very good answer on another question:
[http://stackoverflow.com/questions/7868507/non-trivial-
lazy-...](http://stackoverflow.com/questions/7868507/non-trivial-lazy-
evaluation/7868790#7868790)

------
bhickey

        Lazy languages like Haskell make it a point to be
        referentially transparent, i.e. making sure that the
        order in which expressions are evaluated will never
        affect their result.
    

Isn't referential transparency a consequence of purity, not laziness?

Edit: While I believe the quote is saying (albeit in a peculiar way) that lazy
languages must be referentially transparent, I don't see how this particular
response was arguing in favor of laziness. Referential transparency is great
as an optimization enabler, which provides one compelling case for purity.

Meanwhile I'm working on a nascent project to develop an eager, pure language
that targets LLVM.

~~~
benbataille
>I don't see how this particular response was arguing in favor of laziness.

In a referentially transparent language, you don't care about order of
evaluation because whatever the order you will always end up with exactly the
same result. Thus, losing control of it through laziness doesn't matter.

However, IMHO, the initial argument is kind of wrong. Laziness doesn't really
make you lose control of order of evaluation. You know when expressions are
going to be evaluated : when they are going to be needed. Obviously, when you
use a lot of abstraction you didn't write like in Haskell, it becomes tricky.
But, if you perfectly know how your data structures are processed by the
compiler, you can safely use side effects with laziness. Not that you should,
but you can.

------
rpearl
[http://augustss.blogspot.com/2011/05/more-points-for-lazy-
ev...](http://augustss.blogspot.com/2011/05/more-points-for-lazy-evaluation-
in.html) is a very good discussion of why laziness has some nice semantic
properties from the theory point of view.

And the post it is responding to,
[https://existentialtype.wordpress.com/2011/04/24/the-real-
po...](https://existentialtype.wordpress.com/2011/04/24/the-real-point-of-
laziness/) makes some interesting points against it as well.

------
cpdean
Whoa, did they just lift content directly off Stack?

I guess it's appropriate for an article on laziness.

~~~
nickbarnwell
No; they have an agreement with StackExchange to allow for republishing of the
content (check the byline).

~~~
Moto7451
Actually I think it has more to do with content on Stack sites being freely
available under a creative commons license.

~~~
dllthomas
That would be an agreement to allow for republishing, albeit one offered to
anyone under the same terms.

------
cageface
I'm not so sure I'm convinced by the argument for modularity.

Maybe Haskell makes it easier to write generators of possibly infinite
sequences but people use the producer-consumer model all the time in other
langages to explore potentially large solution spaces generated on demand.

~~~
btilly
That would be the _implementing your own version of laziness_ part of the
modularity argument.

In a language like Haskell you never have to do that. Ever. Because the
language is lazy.

~~~
hythloday
"Implementing your own version of laziness" makes it sound error-prone and
difficult. In fact it's this pattern:

    
    
      def move(board, position, player):
        """
          board is a 11-character string which is 3 lengths of 3 of the characters
            X, O or _, separated by newlines
          turn is either X or O, depending on who moves
        """
        return board[:position] + player + board[position+1:]
    
      def getNextStates(board, player):
        spaces = [ i for i,c in enumerate(board) if c == '_' ]
        return [ move(board, i, player) for i in spaces ]
    
      >>> print board
      X_O
      _OX
      X_O 
      
      >>> for state in getNextStates(board, 'O'):
      ...   print state
      ...   print
      ... 
      XOO
      _OX
      X_O
     
      X_O
      OOX
      X_O
      
      X_O
      _OX
      XOO
    

I doubt most programmers exposed only to imperative programming would even
notice that there was a pattern in what they were implementing, let alone
identify it as laziness.

~~~
btilly
The difficulty depends on the language and on what you're trying to do.

In Python the correct translation is usually to replace return with yield and
work with iterators. In a language without that feature, well,
<http://perl.plover.com/Stream/stream.html> demonstrates what a solution looks
like in Perl. A Java solution to the same issue is going to be..verbose.

~~~
hythloday
I avoided generators because implementing ersatz laziness with lazy language
features seemed like cheating - you can read my Python lists as C arrays, but
it's much harder to map that onto coroutines without significant handwaving.

------
cousin_it
1) Is there a problem that provably requires asymptotically more memory in a
lazy language than in a strict one?

2) Is there a problem that provably requires asymptotically more time in a
pure language than in an impure one?

~~~
Evbn
Any impure algorithm on a system that uses pointers can be made pure with I
think log(n) oberhead, which in practice is usually equivalent to a constant
factor. But in the real world constant factors matter, in time as well as
space (cache locality to do work in chip instead of ram instead of disk
instead of tape)

~~~
gizmo686
Your answer sounds like the solution to the mutable data question, not the
pure/impure question. For the question of memory usage on a lazy language vs a
strict language, there is no asymptotic difference. The strict program can be
viewed as a series of commands, each modifying the state of the program. For
example, consider the following code: int double(int x){ int ans=x; ans=ans
_2; return ans; } The above code can be viewed as: Input state |
execute(ans=x) | execute(ans=2_ x) | execute(return ans) where execute takes
as an input both the state of the program, and a command. (As an aside, this
is the approach of piping a state variable across functions is how Haskell
handles IO)

The above code can also be viewed as: Input | A(x) | B(x) | C(x)

Evaluating this lazily will get you: C . B . A . Input

What the program wants to return is the final output of C. When the program
tries to return C, it will go down the chain of expressions it must evaluate
until it reaches one that it can, in this case A(Input). Once A(Input) is
calculated, we no longer need Input it memory, so it can be garbage collected.
At this point, the program is: C . B . Input' Because Input' is the same size
in memory as Input, we are, at this point, using no more memory than the
strict version of the program. We can continue this argument until we
eventually arrive at the final state, in which we have our answer.

With this, we can see that any strictly evaluated program can be done lazily
with no more than twice the memory requirements (there is a point where we
need both Input, and Input'), which from an asymptotic standpoint is
equvilent.

~~~
cousin_it
Yeah, that seems to work. Thanks!

I still wonder about the mutable data question though. Can we do better than
logarithmic overhead, or is there a problem for which we provably cannot?

------
yason
In my experience, I tend to be of the opinion that lazy, immutable data
structures with eager evaluation bring you most of the benefits of lazy
evaluation with the control of eager evaluation.

Clojure is an example of that. You can plant printfs there and it just works,
but you can also set it to do huge amount of processing on huge amount of data
and eventually just consume and crunch small part of it.

~~~
tome
What are "lazy, immutable datastructures with eager evaluation"?

~~~
chipsy
A lazy data structure exposes access methods that exhibit lazy-evaluator
behavior. For example, you have a "rectangle" structure that contains
position, width and height, and the structure has methods that let you apply
matrix transformations to scale, rotate, and skew the rectangle's corners. The
matrix methods perform the computation, and then cache that result in case
it's needed again.

~~~
Evbn
That is more about functional programming than laziness, but maybe I am
splitting hairs. OpenGL, for example, implements the matrix stack
functionality, and then it only works for one class. A lot of work then to get
ky everywhere you need it.

Clojure is doing a lot of work with fusion and sequences to transfer laziness
from arrays up to the other objects that consume them, but it is harder as you
go farther away.

