Funky haskell lazy list implicit recursion

前端 未结 3 2106
花落未央
花落未央 2021-02-08 05:58

In Haskell, you can build infinite lists due to laziness:

Prelude> let g = 4 : g
Prelude> g !! 0
4
Prelude> take 10 g
[4,4,4,4,4,4,4,4,4,4]
3条回答
  •  傲寒
    傲寒 (楼主)
    2021-02-08 06:32

    "Doesn't exist yet" is not quite right. We try not to think about when values are exist — denotational programming is about eternal unchanging values and equations.

    More concretely, this code is fine:

    Prelude> let x = [(x !! 1) + 1, 3] in x
    [4,3]
    

    You might expect that x !! 1 doesn't exist yet. But here's how an implementation like GHC works.

    When building the list f, it constructs an in-memory object (a "thunk") to represent the expression (x !! 1) + 1. No evaluation of x has occurred yet. It wraps a pointer to this thunk, plus a pointer to 3, into a linked list and hands it off to GHCi's implicit show.

    Now, the show instance for lists has to show the elements one by one. show will demand ("force") the evaluation of the thunk for (x !! 1) + 1, which causes that code to be "entered". By the definition of (+), forcing (x !! 1) + 1 forces x !! 1, which in turn forces two things:

    • the spine of the list (its cons cells) through the second cell, and
    • the value of the second element (the "car" of the second cell, in Lisp terms), because (+) wants to operate on that value.

    And the second value is present by now — it's 3. If it were another thunk we'd force that, and so on. See this blog post for an interesting view on self-referential containers.

    Now, how does the compiled GHC code detect an infinite loop in your other example? When we enter a thunk, we need to remember to come back later and overwrite it with the final value. This is what's meant specifically by "lazy evaluation" as opposed to "non-strict semantics", and it prevents us from duplicating work.

    Anyway, as an optimization when entering a thunk, GHC's runtime will first overwrite it with another object called a "blackhole". We'll come back later and overwrite the blackhole with the final value. But what happens if we enter a blackhole before doing so? That means that evaluating x requires first evaluating x, an unresolvable loop. So entering a blackhole throws an exception.

提交回复
热议问题