您的推断是正确的,display 是一个返回值的函数(除了具有打印到当前输出端口的副作用)。然而,display 的特定调用返回的值是 read-eval-print 循环简单地选择不打印的值,当它作为评估表达式的结果自行发生时。
卡瓦有多个特别的constants;其中之一是#!void,它等同于计算表达式(values) 的结果(这意味着“根本没有值”)。如果您从 read-eval-print 循环中获取值 #!void,则不会打印:
#|kawa:1|# #!void
#|kawa:2|# (values)
#|kawa:3|#
这是因为 Kawa 的 read-eval-print 循环 uses display 打印出表达式计算的值,而 display 在给定 #!void 时将选择不打印任何内容。
在您比较8 与(display 8) 行为的实验的特定情况下,实际上发生的事情存在重大差异。当您向解释器提供任何输入时,它:
- 读取(并编译)输入,
- 将编译后的表达式计算为一个值,然后
- 打印出结果值。
因此,当您输入 8 时,打印将在第 3 步中进行。当您输入 (display 8) 时,将在第 2 步中进行打印,然后第 3 步中的打印什么也不打印(因为值(display 8) 返回的是解释器选择不打印的)。
观察这种区别的一种方法是:根据感兴趣的表达建立一个列表。
#|kawa:1|# (list (display 7) 8 (display 9))
/dev/stdin:1:7: warning - void-valued expression where value is needed
/dev/stdin:1:21: warning - void-valued expression where value is needed
7 9 (#!null 8 #!null)
#|kawa:2|#
在这里我们看到,在评估步骤中,解释器显示7,然后是9,然后构建了一个包含三个元素的列表:#!null、8,然后又是#!null。
Kawa 解释器还警告我们,我们的代码似乎存在问题:Kawa 解释器在读取和编译步骤(发生在评估和打印步骤之前)足够聪明,可以分析潜在问题的代码。在这里,它说“调用display 的结果并不意味着像正常值一样使用”(与数字或字符串相比)。
所以这解释了为什么您会看到一条错误消息(因为它认为调用 display 的结果是无效值),并且它知道对这些值的处理可能与用户的期望不符。 (它还解释了为什么您的示例中的数字8 打印在错误消息之后:因为错误消息是在“读取”步骤期间生成的,但显示发生在“评估”步骤期间,如上所述。
为什么我会说“对这些值的处理可能不符合用户的期望”?好吧,从上面我们运行(list (display 7) 8 (display 9)) 的实验中,您可能推断出评估(display 7) 的结果是#!null。但事实并非如此!
在 Kawa 中,#!null 是一个特殊的常量,它与 #!void 不同。出于某种原因,Kawa 解释器决定,当您将 (display 7) 插入列表构造表达式(或更一般地说,我认为任何期望非 void 值的上下文)时,它可以丢弃 @987654354 的返回值@ 并将 #!null 插入其中。
我为什么这么说?好吧,还有另一种方法可以在 Scheme 中打印出值:您可以使用 write 过程。 display 过程通常用于“人类(或最终用户)可读”输出,而write 过程用于显示更多关于给定数据结构的信息(如果可用)。例如:
#|kawa:1|# (display "Hello World")
Hello World
#|kawa:2|# (write "Hello World")
"Hello World"
#|kawa:3|#
上面display扔掉了我有一个字符串的信息,只关注那个字符串的内容,而write告诉我“我们这里有一个字符串,它有11个字符长。这里是它的内容。”
SO,如果我们 write 调用 display 的结果会怎样?
#|kawa:1|# (write (display 8))
8#!void
#|kawa:2|#
在这里,我们没有收到来自读取和编译步骤的警告。相反,它保留了(display 8) 的值。所以它首先评估(display 8)(将8 打印到输出),然后将(#!void) 生成的值输入到write 调用中。
(我不会声称这是有史以来最清晰的语义。但我的推断是 Kawa 的 warn-void-used 告诉我们允许编译器插入 #!null 代替 #!void 的情况)