How to adapt trampolines to Continuation Passing Style?

前端 未结 3 1494
傲寒
傲寒 2020-11-29 10:54

Here is a naive implementation of a right fold:

const foldr = f => acc => ([x, ...xs]) =>
  x === undefined
    ? acc 
    : f(x) (foldkr(f) (acc) (         


        
3条回答
  •  时光说笑
    2020-11-29 11:28

    yes, yes, and yes (part 2)

    So I believe this answer gets closer to the core of your question – can we make any recursive program stack-safe? Even if recursion isn't in tail position? Even if the host language doesn't have tail-call elimination? Yes. Yes. And yes – with one small requirement...

    The end of my first answer talked about loop as a sort of evaluator and then described a rough idea of how it would be implemented. The theory sounded good but I wanted to make sure the technique works in practice. So here we go!


    non-trivial program

    Fibonacci is great for this. The binary recursion implementation builds a big recursive tree and neither recursive call is in tail position. If we can get this program right, we can have reasonable confidence we implemented loop correctly.

    And here's that small requirement: You cannot call a function to recur. Instead of f (x), you will write call (f, x) –

    const add = (a = 0, b = 0) =>
      a + b
    
    const fib = (init = 0) =>
      loop
        ( (n = init) =>
            n < 2
              ? n
              : add (recur (n - 1), recur (n - 2))
              : call (add, recur (n - 1), recur (n - 2))
        )
    
    fib (10)
    // => 55

    But these call and recur functions are nothing special. They only create ordinary JS objects –

    const call = (f, ...values) =>
      ({ type: call, f, values })
    
    const recur = (...values) =>
      ({ type: recur, values })
    

    So in this program, we have a call that depends on two recurs. Each recur has the potential to spawn yet another call and additional recurs. A non-trivial problem indeed, but in reality we're just dealing with a well-defined recursive data structure.


    writing loop

    If loop is going to process this recursive data structure, it'll be easiest if we can write loop as a recursive program. But aren't we just going to run into a stack-overflow somewhere else then? Let's find out!

    // loop : (unit -> 'a expr) -> 'a
    const loop = f =>
    { // aux1 : ('a expr, 'a -> 'b) -> 'b 
      const aux1 = (expr = {}, k = identity) =>
        expr.type === recur
          ? // todo: when given { type: recur, ... }
      : expr.type === call
          ? // todo: when given { type: call, ... }
      : k (expr) // default: non-tagged value; no further evaluation necessary
    
      return aux1 (f ())
    }
    

    So loop takes a function to loop, f. We expect f to return an ordinary JS value when the computation is completed. Otherwise return either call or recur to grow the computation.

    These todos are somewhat trivial to fill in. Let's do that now –

    // loop : (unit -> 'a expr) -> 'a
    const loop = f =>
    { // aux1 : ('a expr, 'a -> 'b) -> 'b 
      const aux1 = (expr = {}, k = identity) =>
        expr.type === recur
          ? aux (expr.values, values => aux1 (f (...values), k))
      : expr.type === call
          ? aux (expr.values, values => aux1 (expr.f (...values), k))
      : k (expr)
    
      // aux : (('a expr) array, 'a array -> 'b) -> 'b
      const aux = (exprs = [], k) =>
        // todo: implement me
    
      return aux1 (f ())
    }
    

    So intuitively, aux1 (“auxiliary one”) is the magic wand we wave over one expression, expr, and the result comes back in the continuation. In other words –

    // evaluate expr to get the result
    aux1 (expr, result => ...)
    

    To evaluate recur or call, we must first evaluate the corresponding values. We wish we could write something like –

    // can't do this!
    const r =
      expr.values .map (v => aux1 (v, ...))
    
    return k (expr.f (...r))
    

    What would the continuation ... be? We can't call aux1 in .map like that. Instead, we need another magic wand that can take an array of expressions, and pass the resulting values to its continuation; such as aux –

    // evaluate each expression and get all results as array
    aux (expr.values, values => ...)
    

    meat & potatoes

    Ok, this is the probably the toughest part of the problem. For each expression in the input array, we have to call aux1 and chain the continuation to the next expression, finally passing the values to the user-supplied continuation, k –

    // aux : (('a expr) array, 'a array -> 'b) -> 'b
    const aux = (exprs = [], k) =>
      exprs.reduce
        ( (mr, e) =>
            k => mr (r => aux1 (e, x => k ([ ...r, x ])))
        , k => k ([])
        )
        (k)
    

    We won't end up using this, but it helps to see what we're doing in aux expressed as an ordinary reduce and append –

    // cont : 'a -> ('a -> 'b) -> 'b
    const cont = x =>
      k => k (x)
    
    // append : ('a array, 'a) -> 'a array
    const append = (xs, x) =>
      [ ...xs, x ]
    
    // lift2 : (('a, 'b) -> 'c, 'a cont, 'b cont) -> 'c cont
    const lift2 = (f, mx, my) =>
      k => mx (x => my (y => k (f (x, y))))
    
    // aux : (('a expr) array, 'a array -> 'b) -> 'b
    const aux = (exprs = [], k) =>
      exprs.reduce
        ( (mr, e) =>
            lift2 (append, mr, k => aux1 (e, k))
        , cont ([])
        )
    

    Putting it all together we get –

    // identity : 'a -> 'a
    const identity = x =>
      x
    
    // loop : (unit -> 'a expr) -> 'a
    const loop = f =>
    { // aux1 : ('a expr, 'a -> 'b) -> 'b 
      const aux1 = (expr = {}, k = identity) =>
        expr.type === recur
          ? aux (expr.values, values => aux1 (f (...values), k))
      : expr.type === call
          ? aux (expr.values, values => aux1 (expr.f (...values), k))
      : k (expr)
    
      // aux : (('a expr) array, 'a array -> 'b) -> 'b
      const aux = (exprs = [], k) =>
        exprs.reduce
          ( (mr, e) =>
              k => mr (r => aux1 (e, x => k ([ ...r, x ])))
          , k => k ([])
          )
          (k)
    
      return aux1 (f ())
    }
    

    Time for a little celebration –

    fib (10)
    // => 55
    

    But only a little –

    fib (30)
    // => RangeError: Maximum call stack size exceeded
    

    your original problem

    Before we attempt to fix loop, let's revisit the program in your question, foldr, and see how it's expressed using loop, call, and recur –

    const foldr = (f, init, xs = []) =>
      loop
        ( (i = 0) =>
            i >= xs.length
              ? init
              : f (recur (i + 1), xs[i])
              : call (f, recur (i + 1), xs[i])
        )

    And how does it work?

    // small : number array
    const small =
      [ 1, 2, 3 ]
    
    // large : number array
    const large =
      Array .from (Array (2e4), (_, n) => n + 1)
    
    foldr ((a, b) => `(${a}, ${b})`, 0, small)
    // => (((0, 3), 2), 1)
    
    foldr ((a, b) => `(${a}, ${b})`, 0, large)
    // => RangeError: Maximum call stack size exceeded
    

    Okay, it works but for small but it blows up the stack for large. But this is what we expected, right? After all, loop is just an ordinary recursive function, bound for an inevitable stack-overflow... right?

    Before we go on, verify the results to this point in your own browser –

    // call : (* -> 'a expr, *) -> 'a expr
    const call = (f, ...values) =>
      ({ type: call, f, values })
    
    // recur : * -> 'a expr
    const recur = (...values) =>
      ({ type: recur, values })
    
    // identity : 'a -> 'a
    const identity = x =>
      x
    
    // loop : (unit -> 'a expr) -> 'a
    const loop = f =>
    { // aux1 : ('a expr, 'a -> 'b) -> 'b
      const aux1 = (expr = {}, k = identity) =>
        expr.type === recur
          ? aux (expr.values, values => aux1 (f (...values), k))
      : expr.type === call
          ? aux (expr.values, values => aux1 (expr.f (...values), k))
      : k (expr)
    
      // aux : (('a expr) array, 'a array -> 'b) -> 'b
      const aux = (exprs = [], k) =>
        exprs.reduce
          ( (mr, e) =>
              k => mr (r => aux1 (e, x => k ([ ...r, x ])))
          , k => k ([])
          )
          (k)
    
      return aux1 (f ())
    }
    
    // fib : number -> number
    const fib = (init = 0) =>
      loop
        ( (n = init) =>
            n < 2
              ? n
              : call
                  ( (a, b) => a + b
                  , recur (n - 1)
                  , recur (n - 2)
                  )
        )
    
    // foldr : (('b, 'a) -> 'b, 'b, 'a array) -> 'b
    const foldr = (f, init, xs = []) =>
      loop
        ( (i = 0) =>
            i >= xs.length
              ? init
              : call (f, recur (i + 1), xs[i])
        )
    
    // small : number array
    const small =
      [ 1, 2, 3 ]
    
    // large : number array
    const large =
      Array .from (Array (2e4), (_, n) => n + 1)
    
    console .log (fib (10))
    // 55
    
    console .log (foldr ((a, b) => `(${a}, ${b})`, 0, small))
    // (((0, 3), 2), 1)
    
    console .log (foldr ((a, b) => `(${a}, ${b})`, 0, large))
    // RangeError: Maximum call stack size exc


    bouncing loops

    I have too many answers on converting functions to CPS and bouncing them using trampolines. This answer isn't going focus on that much. Above we have aux1 and aux as CPS tail-recursive functions. The following transformation can be done in a mechanical way.

    Like we did in the other answer, for every function call we find, f (x), convert it to call (f, x) –

    // loop : (unit -> 'a expr) -> 'a
    const loop = f =>
    { // aux1 : ('a expr, 'a -> 'b) -> 'b
      const aux1 = (expr = {}, k = identity) =>
        expr.type === recur
          ? call (aux, expr.values, values => call (aux1, f (...values), k))
      : expr.type === call
          ? call (aux, expr.values, values => call (aux1, expr.f (...values), k))
      : call (k, expr)
    
      // aux : (('a expr) array, 'a array -> 'b) -> 'b
      const aux = (exprs = [], k) =>
        call
          ( exprs.reduce
              ( (mr, e) =>
                  k => call (mr, r => call (aux1, e, x => call (k, [ ...r, x ])))
              , k => call (k, [])
              )
          , k
          )
    
      return aux1 (f ())
      return run (aux1 (f ()))
    }

    Wrap the return in run, which is a simplified trampoline –

    // run : * -> *
    const run = r =>
    { while (r && r.type === call)
        r = r.f (...r.values)
      return r
    }
    

    And how does it work now?

    // small : number array
    const small =
      [ 1, 2, 3 ]
    
    // large : number array
    const large =
      Array .from (Array (2e4), (_, n) => n + 1)
    
    fib (30)
    // 832040
    
    foldr ((a, b) => `(${a}, ${b})`, 0, small)
    // => (((0, 3), 2), 1)
    
    foldr ((a, b) => `(${a}, ${b})`, 0, large)
    // => (Go and see for yourself...)
    

    Witness stack-safe recursion in any JavaScript program by expanding and running the snippet below –

    // call : (* -> 'a expr, *) -> 'a expr
    const call = (f, ...values) =>
      ({ type: call, f, values })
    
    // recur : * -> 'a expr
    const recur = (...values) =>
      ({ type: recur, values })
    
    // identity : 'a -> 'a
    const identity = x =>
      x
    
    // loop : (unit -> 'a expr) -> 'a
    const loop = f =>
    { // aux1 : ('a expr, 'a -> 'b) -> 'b
      const aux1 = (expr = {}, k = identity) =>
        expr.type === recur
          ? call (aux, expr.values, values => call (aux1, f (...values), k))
      : expr.type === call
          ? call (aux, expr.values, values => call (aux1, expr.f (...values), k))
      : call (k, expr)
    
      // aux : (('a expr) array, 'a array -> 'b) -> 'b
      const aux = (exprs = [], k) =>
        call
          ( exprs.reduce
              ( (mr, e) =>
                  k => call (mr, r => call (aux1, e, x => call (k, [ ...r, x ])))
              , k => call (k, [])
              )
          , k
          )
    
      return run (aux1 (f ()))
    }
    
    // run : * -> *
    const run = r =>
    { while (r && r.type === call)
        r = r.f (...r.values)
      return r
    }
    
    // fib : number -> number
    const fib = (init = 0) =>
      loop
        ( (n = init) =>
            n < 2
              ? n
              : call
                  ( (a, b) => a + b
                  , recur (n - 1)
                  , recur (n - 2)
                  )
        )
    
    // foldr : (('b, 'a) -> 'b, 'b, 'a array) -> 'b
    const foldr = (f, init, xs = []) =>
      loop
        ( (i = 0) =>
            i >= xs.length
              ? init
              : call (f, recur (i + 1), xs[i])
        )
    
    // small : number array
    const small =
      [ 1, 2, 3 ]
    
    // large : number array
    const large =
      Array .from (Array (2e4), (_, n) => n + 1)
    
    console .log (fib (30))
    // 832040
    
    console .log (foldr ((a, b) => `(${a}, ${b})`, 0, small))
    // (((0, 3), 2), 1)
    
    console .log (foldr ((a, b) => `(${a}, ${b})`, 0, large))
    // YES! YES! YES!


    evaluation visualisation

    Let's evaluate a simple expression using foldr and see if we can peer into how loop does its magic –

    const add = (a, b) =>
      a + b
    
    foldr (add, 'z', [ 'a', 'b' ])
    // => 'zba'
    

    You can follow along by pasting this in a text-editor that supports bracket highlighting –

    // =>
    aux1
      ( call (add, recur (1), 'a')
      , identity
      )
    
    // =>
    aux1
      ( { call
        , f: add
        , values:
            [ { recur, values: [ 1 ]  }
            , 'a'
            ]
        }
      , identity
      )
    
    // =>
    aux
      ( [ { recur, values: [ 1 ]  }
        , 'a'
        ]
      , values => aux1 (add (...values), identity)
      )
    
    // =>
    [ { recur, values: [ 1 ]  }
    , 'a'
    ]
    .reduce
      ( (mr, e) =>
          k => mr (r => aux1 (e, x => k ([ ...r, x ])))
      , k => k ([])
      )
    (values => aux1 (add (...values), identity))
    
    // beta reduce outermost k
    (k => (k => (k => k ([])) (r => aux1 ({ recur, values: [ 1 ]  }, x => k ([ ...r, x ])))) (r => aux1 ('a', x => k ([ ...r, x ])))) (values => aux1 (add (...values), identity))
    
    // beta reduce outermost k
    (k => (k => k ([])) (r => aux1 ({ recur, values: [ 1 ]  }, x => k ([ ...r, x ])))) (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ])))
    
    // beta reduce outermost k
    (k => k ([])) (r => aux1 ({ recur, values: [ 1 ]  }, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...r, x ])))
    
    // beta reduce outermost r
    (r => aux1 ({ recur, values: [ 1 ]  }, x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...r, x ]))) ([])
    
    // =>
    aux1
      ( { recur, values: [ 1 ]  }
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux
      ( [ 1 ]
      , values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))
      )
    
    // =>
    [ 1 ]
    .reduce
      ( (mr, e) =>
          k => mr (r => aux1 (e, x => k ([ ...r, x ])))
      , k => k ([])
      )
    (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
    
    // beta reduce outermost k
    (k => (k => k ([])) (r => aux1 (1, x => k ([ ...r, x ])))) (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
    
    // beta reduce outermost k
    (k => k ([])) (r => aux1 (1, x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ])))
    
    // beta reduce outermost r
    (r => aux1 (1, x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([])
    
    // =>
    aux1
      ( 1
      , x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])
      )
    
    // beta reduce outermost x
    (x => (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])) (1)
    
    // =>
    (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], 1 ])
    
    // =>
    (values => aux1 (f (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ 1 ])
    
    // =>
    aux1
      ( f (...[ 1 ])
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux1
      ( f (1)
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux1
      ( call (add, recur (2), 'b')
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux1
      ( { call
        , f: add
        , values:
            [ { recur, values: [ 2 ] }
            , 'b'
            ]
        }
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux
      ( [ { recur, values: [ 2 ] }
        , 'b'
        ]
      , values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))
      )
    
    // =>
    [ { recur, values: [ 2 ] }
    , 'b'
    ]
    .reduce
      ( (mr, e) =>
          k => mr (r => aux1 (e, x => k ([ ...r, x ])))
      , k => k ([])
      )
    (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
    
    // beta reduce outermost k
    (k => (k => (k => k ([])) (r => aux1 ({ recur, values: [ 2 ] }, x => k ([ ...r, x ])))) (r => aux1 ('b', x => k ([ ...r, x ])))) (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ]))))
    
    // beta reduce outermost k
    (k => (k => k ([])) (r => aux1 ({ recur, values: [ 2 ] }, x => k ([ ...r, x ])))) (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ])))
    
    // beta reduce outermost k
    (k => k ([])) (r => aux1 ({ recur, values: [ 2 ] }, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...r, x ])))
    
    // beta reduce outermost r
    (r => aux1 ({ recur, values: [ 2 ] }, x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...r, x ]))) ([])
    
    // =>
    aux1
      ( { recur, values: [ 2 ] }
      , x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux
      ( [ 2 ]
      , values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))
      )
    
    // =>
    [ 2 ]
    .reduce
      ( (mr, e) =>
          k => mr (r => aux1 (e, x => k ([ ...r, x ])))
      , k => k ([])
      )
    (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ]))))
    
    // beta reduce outermost k
    (k => (k => k ([])) (r => aux1 (2, x => k ([ ...r, x ])))) (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ]))))
    
    // beta reduce outermost k
    (k => k ([])) (r => aux1 (2, x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ])))
    
    // beta reduce outermost r
    (r => aux1 (2, x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([])
    
    // =>
    aux1
      ( 2
      , x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])
      )
    
    // beta reduce outermost x
    (x => (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], x ])) (2)
    
    // spread []
    (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[], 2 ])
    
    // beta reduce outermost values
    (values => aux1 (f (...values), (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])))) ([ 2 ])
    
    // spread [ 2 ]
    aux1
      ( f (...[ 2 ])
      , x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux1
      ( f (2)
      , x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux1
      ( 'z'
      , x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // beta reduce outermost x
    (x => (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], x ])) ('z')
    
    // spread []
    (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ ...[], 'z' ])
    
    // beta reduce outermost r
    (r => aux1 ('b', x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...r, x ]))) ([ 'z' ])
    
    // =>
    aux1
      ( 'b'
      , x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[ 'z' ], x ])
      )
    
    // beta reduce outermost x
    (x => (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[ 'z' ], x ])) ('b')
    
    // spread ['z']
    (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ ...[ 'z' ], 'b' ])
    
    // beta reduce outermost values
    (values => aux1 (add (...values), (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])))) ([ 'z', 'b' ])
    
    // =>
    aux1
      ( add (...[ 'z', 'b' ])
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux1
      ( add ('z', 'b')
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // =>
    aux1
      ( 'zb'
      , x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])
      )
    
    // beta reduce outermost x
    (x => (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], x ])) ('zb')
    
    // spead []
    (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ ...[], 'zb' ])
    
    // beta reduce outermost r
    (r => aux1 ('a', x => (values => aux1 (add (...values), identity)) ([ ...r, x ]))) ([ 'zb' ])
    
    // =>
    aux1
      ( 'a'
      , x => (values => aux1 (f (...values), identity)) ([ ...[ 'zb' ], x ])
      )
    
    // beta reduce outermost x
    (x => (values => aux1 (f (...values), identity)) ([ ...[ 'zb' ], x ])) ('a')
    
    // spead ['zb']
    (values => aux1 (f (...values), identity)) ([ ...[ 'zb' ], 'a' ])
    
    // beta reduce values
    (values => aux1 (f (...values), identity)) ([ 'zb', 'a' ])
    
    // spread [ 'zb', 'a' ]
    aux1
      ( f (...[ 'zb', 'a' ])
      , identity
      )
    
    // =>
    aux1
      ( f ('zb', 'a')
      , identity
      )
    
    // =>
    aux1
      ( 'zba'
      , identity
      )
    
    // =>
    identity ('zba')
    
    // =>
    'zba'
    

    Closures sure are amazing. Above we can confirm that CPS keeps the computation flat: we see either aux, aux1, or a simple beta reduction in each step. This is what makes it possible for us to put loop on a trampoline.

    And this is where we double-dip on call. We use call to create an object for our looping computations, but aux and aux1 also spit out calls that are handled by run. I could've (maybe should've) made a different tag for this, but call was sufficiently generic that I could use it in both places.

    So above where we see aux (...) and aux1 (...) and beta reductions (x => ...) (...), we simply replace them with call (aux, ...), call (aux1, ...) and call (x => ..., ...) respectively. Pass these to run and that's it — Stack-safe recursion in any shape or form. Simple as that

提交回复
热议问题