如何跳过 scheme 中的 if 语句

How to skip on an if statement in scheme

我有以下函数在方案中进行递归倒计时:

(define (countdown n)
   (display n)
   (if (> n 0)
      (countdown (- n 1))
       0 ; how to make this "do nothing" and *not* print zero?
    )
)

还有一个 运行:

(countdown 7)
(countdown 6)
765432100
65432100

您会注意到倒计时结束后,它会打印出零(或者 'evaluates' 打印为零)。我怎样才能摆脱它,让它只打印“7654321”然后退出?

此外,如果我尝试将两个语句都放在 if:

(define (countdown n)
   (if (> n 0) (
      (display n)
      (countdown (- n 1)))
       0
    )
)
(countdown 7)

我收到以下错误:

7654321TypeError: Cannot read property 'toString' of undefined

Current Eval Stack:
-------------------------
0: (countdown (- n 1))
1: (countdown (- n 1))
2: (countdown (- n 1))
3: (countdown (- n 1))
4: (countdown (- n 1))
5: (countdown (- n 1))
6: (countdown 7)

这到底是什么意思?

第一个程序

countdown过程总是打印它的参数,因为(display n)首先出现在过程中。当 n 为零时,它会在该调用中打印出来,然后 if 表达式检查 n 是否为零。这解释了为什么第一次打印 0

0 第二次打印,不是由程序打印,而是由 REPL 打印。当您通过在 REPL 中调用 (countdown 7) 来调用它时,您就是 运行 REPL(Read Evaluate Print Loop)中的程序。 REPL 计算表达式,并在 REPL 中打印结果 countdown 过程的最后一个表达式是从过程 returned 到 REPL,它会为您打印出来。因为它是一个 REPL,做 REPL 做的事情。如果您 运行 从命令行将此程序作为脚本,您将看不到最后一个零。

现在,一个文体注意事项:不要在口齿不清时留下悬挂的括号。这样做是不合常理的,而且当代码变大时,阅读带有这种 er运行t 括号的 lisp 代码会变得更加困难。有一些罕见的情况是合适的,但通常你应该把右括号收集在一起。

修复发布的代码的第一次尝试可能如下所示:

(define (countdown n)
  (if (> n 0)
      (begin (display n)
             (countdown (- n 1)))
      0))

这里需要begin形式,因为if只对它的每只手臂计算一个表达式,需要计算两个表达式才能打印n 并递归调用 countdown。该程序将在 REPL 中打印一次 0(因为它仍在 returning 0),而根本不会在命令行脚本中打印。

countdown 过程仍在 return 生成一个数字,REPL 仍在打印该数字。 Scheme 中的过程总是 return 值,所以无论如何都必须从 countdown 编辑一些东西。可以下注并省略 if 形式的第二臂:

(define (countdown n)
  (if (> n 0)
      (begin (display n)
             (countdown (- n 1)))))

这是完全合法的 Scheme,但是 if 形式 return 是一个 未指定的 值,当它的第一个测试评估为 #f 并且第二只手臂被省略了。当我在 Chez Scheme 中 运行 this 时,无法打印的值 #<void> 被 returned,所以行为正是 OP 所期望的。

但是可能有更好的写法;许多方案(例如 Chez Scheme)有一个 when 形式,只测试一个表达式,即 when 是单臂的。 when 当其单一测试评估为 #f 时,还有 returns 未指定的值。但是,when 有一个额外的优势,因为它可以在测试评估为真时评估多个表达式;这意味着使用 when:

时不需要 begin 形式
(define (countdown n)
  (when (> n 0)
    (display n)
    (countdown (- n 1))))

这在 REPL 中按预期工作:

> (countdown 7)
7654321>

第二期节目[=​​97=]
(define (countdown n)
  (if (> n 0)
      ((display n)
       (countdown (- n 1)))
      0))

如前所述,if 每只手臂评估 一个 表达式。我们已经看到可以使用 begin 形式将表达式组合在一起。这里尝试通过用括号将两个表达式组合起来,但这行不通。总之,括号在lisps中是有语义的,不能随意加进去。

在 Scheme 中,看起来像 (something something-else-1 ...) 的形式被解释为过程调用,除非 something 被绑定为语法关键字(即,除非 something 是宏标识符) .过程调用总是以未指定的顺序计算它们的运算符表达式和所有参数表达式。由于评估顺序取决于实现,详细行为会有所不同,但请考虑:

((display n) (countdown (- n 1)))

当计算 (display n) 时(此时计算 n 和计算 display,顺序未指定,结果用于进行过程调用),调用的结果是 return 一个未指定的值,并导致副作用,即打印 n 的值。 display 是一个始终 returns 未指定值的过程。

当表达式(countdown (- n 1))被求值时,控制将传递给下一个递归调用,n每次减1,直到n为0。此时, 0 被 return 编辑到之前的调用 ((display 1) (countdown 0)),所以我们现在有 ((display 1) 0).

(display n) 可能尚未以任何形式进行评估; Chez Scheme 就是这种情况。因此,在那种情况下,((display 1) 0) --> (#<some-unspecified-value> 0) 现在会产生打印 1 的副作用。应该清楚的是,尝试使用程序 #<some-unspecified-value> 并不是通往快乐的道路。

TypeError: Cannot read property 'toString' of undefined

或者,Scheme 实现可能会在参数表达式之前评估运算符表达式,在这种情况下,倒计时数字将在遇到错误之前全部打印出来。 OP 在错误消息之前显示 7654321,这似乎表明这是 OP 方案中的行为。