Recursion 在递归函数中处理多个可能的返回类型

Recursion 在递归函数中处理多个可能的返回类型,recursion,boolean,racket,htdp,Recursion,Boolean,Racket,Htdp,我正在使用racketisl+编写一个递归,该递归计算一系列结构。如果结构在某些参数上失败,我想返回一个值#false。然而,在递归过程中,我知道计算机得到了1+1+1+1+1+1+1+1+1+#false,这给了我一个错误 有没有一种方法可以让我声明,如果发现错误只是从原始调用返回一个值#false?当一个值从递归过程的某个过程返回时,接收该值的过程必须能够处理它。如果递归过程可以返回两种不同类型中的任何一种,例如布尔值或数字,则该过程需要在使用需要单个类型的过程处理返回值之前测试返回值 使用

我正在使用racketisl+编写一个递归,该递归计算一系列结构。如果结构在某些参数上失败,我想返回一个值
#false
。然而,在递归过程中,我知道计算机得到了
1+1+1+1+1+1+1+1+1+#false
,这给了我一个错误


有没有一种方法可以让我声明,如果发现错误只是从原始调用返回一个值
#false

当一个值从递归过程的某个过程返回时,接收该值的过程必须能够处理它。如果递归过程可以返回两种不同类型中的任何一种,例如布尔值或数字,则该过程需要在使用需要单个类型的过程处理返回值之前测试返回值

使用延续 有可能得到一个延续,然后用它来逃避递归过程。这可能不是OP想要的解决方案;我不相信HTDP学生语言提供了一种获得延续的方法。在普通球拍中,您可以使用
call/cc
获取续集:

(define (count-numbers-or-f xs)
  (call/cc
   (lambda (break)
     (define (loop xs)
       (cond ((null? xs) 0)
             ((number? (car xs))
              (+ 1 (loop (cdr xs))))
             (else (break #f))))
     (loop xs))))
这里不是开发continuations细节的地方,但简单地说,
call/cc
对其进行了安排,以便上述代码中的
break
是一个转义过程,当调用时,该过程将其参数(
#f
此处)返回到与调用
call/cc
相关的计算的continuation。这里,当输入的第一个元素是一个
数字时?
,1被添加到剩余输入的计数结果中;但是,当输入的第一个元素不是
数字?
(并且输入不是空列表)时,将调用
中断
过程。当在
循环
描述的递归过程中调用
中断
时,控制跳到该延续,该延续在调用
调用/cc
之后,从而逃避递归过程;值
#f
被赋予延续符,因此
#f
随后由
count-numbers-or-f
返回

不延续 Continuations是从循环中实现这种非局部退出的经典方法,但通过一些仔细的设计,使用不太奇特的方法,您可以得到类似的结果:

(define (count-numbers-or-f-1 xs)
  (cond ((null? xs) 0)
        ((not (number? (car xs))) #f)
        (else
         (let ((r (count-numbers-or-f-1 (cdr xs))))
           (if (number? r)
               (+ 1 r)
               #f)))))
这里,如果
xs
的车不是一个数字(并且
xs
不是一个空列表),那么
#f
将返回给前一个调用者。否则,
r
表示在
xs
的cdr上调用
count-numbers-of-f-1
的结果。如果
r
不是数字(因为后续调用者遇到非数字元素并返回
#f
),则返回
#f
。否则,加法过程会增长

这样做的结果是,如果遇到一个不是数字的元素,
#f
将立即通过所有以前的堆栈帧传回原始调用,否则将执行求和。设计这样的过程很容易出错,使它们看起来可以工作,但通过遍历所有输入(或更糟的情况)来完成大量不必要的工作。请参阅答案的结尾,讨论此处可能出现的问题

比较 上述任一定义均适用:

scratch.rkt>(count-numbers-or-f'(1234))
4.
scratch.rkt>(count-numbers-or-f'(1 2 x 3 4))
#f
scratch.rkt>(count-numbers-or-f-1'(1234))
4.
scratch.rkt>(计数-数字-或-f-1'(1 2 x 3 4))
#f
以下是第二个版本的一些痕迹:

(define (count-numbers-or-f-2 xs)
  (if (null? xs)
      0
      (let ((x (car xs))
            (r (count-numbers-or-f-2 (cdr xs))))
        (if (and (number? x) (number? r))
            (+ 1 r)
            #f))))
scratch.rkt>(count-numbers-or-f-1'(1 2 3 4 5))
>(计数数字或f-1’(1 2 3 4 5))
>(计数数字或f-1’(2 3 4 5))
>>(计数编号或f-1'(3 4 5))
>>(计数编号或f-1’(4-5))
>>>(计数编号或f-1'(5))
>>>(计数编号或f-1’())
< < < 0
<<(计数数字或f-1'(2 3 x 4 5))
>>(计数编号或f-1'(3 x 4 5))
>>(计数编号或f-1'(x 4 5))
<<#f;从(计数编号或f-1’(x 4 5))返回
<(计数数字或f'(1 2 3 x 4 5))
>(循环(1 2 3 x 4 5)#
>(循环(2 3 x 4 5)#
>>(循环(3 x 4 5)#
>>(循环’(x 4 5)#);循环不返回
(计数数字或f-2’(1 2 3 x 4 5))
>(计数数字或f-2’(2 3 x 4 5))
>>(计数编号或f-2'(3 x 4 5))
>>(计数编号或f-2'(x 4 5))
>>>(计数-编号-或-f-2'(4-5))
>>>(计数-编号-或-f-2'(5))
>>>>(计数-数字-或-f-2'())
<>(计数数字或f-3'(x 4 5))
<<#f
<(计数-编号-或-f-3'(2-3))
>>(计数-编号-或-f-3'(3))
>>(计数-数字-或-f-3'())
< < 0
>>(计数-数字-或-f-3'())
< < 0
<>(计数编号或f-3'(3))
>>(计数-数字-或-f-3'())
< < 0
>>(计数-数字-或-f-3'())
< < 0
<(计数-编号-或-f-3'(2-3))
>>(计数-编号-或-f-3'(3))
>>(计数-数字-或-f-3'())
< < 0
>>(计数-数字-或-f-3'())
< < 0
<>(计数编号或f-3'(3))
>>(计数-数字-或-f-3'())
< < 0
>>(计数-数字-或-f-3'())
< < 0
<(时间测试计数-编号-或-f-1
(附加(生成列表400000个值)
"(十)
(生成列表400000个值)))
cpu时间:5实时:5 gc时间:0
#f
即使在遇到输入失败的情况下,也会遍历所有输入的错误版本更糟糕:

scratch.rkt>(时间测试计数-number-or-f-2
(附加(生成列表400000个值)
"(十)
(生成列表400000个值)))
cpu时间:28实时:28 gc时间:9
#f
它看起来更好的非转义实现,
count-numbers-or-f-1
,比
count-numbers-or-f
(使用连续体)慢5倍左右,而实现较差的
count-numbers-of-f-2
count-numbers-or-f
慢28倍左右。但至少所有人都是这样
(define (count-numbers-or-f-2 xs)
  (if (null? xs)
      0
      (let ((x (car xs))
            (r (count-numbers-or-f-2 (cdr xs))))
        (if (and (number? x) (number? r))
            (+ 1 r)
            #f))))
(define (count-numbers-or-f-3 xs)
  (if (null? xs)
      0
      (if (and (number? (car xs))
               (number? (count-numbers-or-f-3 (cdr xs))))
          (+ 1 (count-numbers-or-f-3 (cdr xs)))
          #f)))
(define (time-test f xs)
  (collect-garbage 'major)
  (time (f xs)))