Funky haskell lazy list implicit recursion

前端 未结 3 2152
花落未央
花落未央 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:39

    In this case, a picture may tell a thousand words.

    First, remember how cons (the (:) list constructor) works. It's a pair of two things: an element, and a reference to a list tail (which is either another cons, or []).

    As you should know, when you say [1, 2, 3], it's just a shortcut for (1:(2:(3:[]))) or 1:2:3:[]. If you visualize each cons pair as a box with two slots, this expression look like:

    ┌───┬──┐  ┌───┬──┐  ┌───┬──┐  ┌────┐
    │ 1 │ ─┼─>│ 2 │ ─┼─>│ 3 │ ─┼─>│ [] │
    └───┴──┘  └───┴──┘  └───┴──┘  └────┘
    

    One loop

    When you say g = 4 : g, you're not really building an "infinite" list, you're building a circular list: g is defined as a cons whose tail reference simply points back to g itself:

    ┌──────────┐
    │ ┌───┬──┐ │
    └>│ 4 │ ─┼─┘
      └───┴──┘
    

    This actually has nothing to do with laziness, and everything with self-reference: for example, you can do the same thing in (eager) Common Lisp using syntax like '#1=(4 . #1#) (where the #1 is like g).

    Whether you say g !! 0, or g !! 1000000000000, g never grows: (!!) simply runs around the loop in-place, for as many times as you told it to, until it exhausts itself and returns the element, 4.

    Two loops

    When you say f = (f !! 10) : f, the same thing happens—except now, the element slot contains a different expression than 4:

    ┌──────────┐
    │ ┌───┬──┐ │
    └>│ ╷ │ ─┼─┘
      └─┼─┴──┘
        │
        │ ┌───────────┐
        └>│ (f !! 10) │
          └───────────┘
    

    Crucially, this sub-expression also refers back to f, just like the tail does:

     ┌──────────┐
     │ ┌───┬──┐ │
    ┌┴>│ ╷ │ ─┼─┘
    │  └─┼─┴──┘
    │    │
    │    │ ┌───────────┐
    │    └>│ (f !! 10) │
    │      └──┼────────┘
    └─────────┘
    

    So, when you ask for f !! n, (!!) will first run around the top loop n times, then return the element, as it did for g. However, instead of escaping the loop, (f !! 10) just re-enters it, and the process repeats itself: looping 10 times around the top, then once around the bottom, and back.

提交回复
热议问题