How to make this Scheme function not tail recursive?

南楼画角 提交于 2020-03-04 21:27:21

问题


I can't figure out how can I make this tail recursive Scheme function not tail recursive anymore. Anyone can help me?

(define (foldrecl f x u)
  (if (null? x)
      u 
      (foldrecl f (cdr x) (f (car x) u))))

回答1:


left folds are inheritly iterative, but you can easily make them recursive by adding a continuation. eg.

(let ((value expresion-that-calculates))
  value)

So in your case:

(define (foldrecl f x u)
  (if (null? x)
      u 
      (let ((result (foldrecl f (cdr x) (f (car x) u))))
        result)))

While this looks promising it does not guarantee that a smart Scheme implementation figures out that result is just returned and make it a tail call instead. Right folds are easier since they are inherently recursive:

(define (fold-right proc tail lst)
  (if (null? lst)
      tail
      (proc (car lst)
            (fold-right proc tail (cdr lst)))))

Here you clearly see the recursive part needs to become a argument to cons and thus never in tail position unless it is the base case.

Also notice it's slightly simpler to see what arguments goes where when the procedure is called proc, the tail of the result tail and the list argument lst. You don't even need to read my code to know how to use it, but yours I have no idea what x and u and ti doesn't help that the argument order doesn't follow any fold implementations known in Scheme.




回答2:


The recursive call is in tail position, so put it inside another procedure call like this:

(define (identity x) x)

(define (foldrecl f x u)
  (if (null? x)
      u 
      (identity (foldrecl f (cdr x) (f (car x) u)))))

now the recursive call is not in tail position, it is not tail recursive anymore.

A compiler is allowed to optimize away the identity function if it knows that it does nothing but hopefully it wont.




回答3:


Instead of doing, compose a plan for doing it; only in the end, do:

(define (foldreclr f xs a)
  (define (go xs)
     (if (null? xs) 
        (lambda (a) a)
        (let ((r (go (cdr xs))))    ; first, recursive call;
           (lambda                  ; afterwards, return a plan:
                  (a)               ;    given an a, to
             (r                     ;    perform the plan for (cdr xs)
               (f (car xs) a))))))  ;    AFTER processing (car x) and a.
  ((go xs)                          ; when the overall plan is ready,
           a))                      ;    use it with the supplied value

The internal function go follows the right fold pattern. It makes the recursive call first, and only afterwards it composes and returns a value, the plan to first combine the list's head element with the accumulator value, and then perform the plan for the list's tail -- just like the original foldrecl would do.

When the whole list is turned into a plan of action, that action is finally performed to transform the supplied initial accumulator value -- performing the same calculation as the original foldrecl left fold.

This is known as leaning so far right you come back left again.(*)

> (foldreclr - (list 1 2 3 4) 0)   ; 4-(3-(2-(1-0)))
2
> (foldreclr - (list 4 3 2 1) 0)   ; 1-(2-(3-(4-0)))
-2

See also:

  • Foldl as foldr
  • (*) Evolution of a Haskell programmer (fun read)

(sorry, these are in Haskell, but Haskell is a Lisp too.)



来源:https://stackoverflow.com/questions/58125578/how-to-make-this-scheme-function-not-tail-recursive

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!