您的推断是正确的,它display
是一个返回值的函数(除了具有打印到当前输出端口的副作用)。然而,此特定调用display
返回的值是 read-eval-print 循环简单地选择不打印的值,当它作为评估表达式的结果自行发生时。
Kawa 有许多特殊的常数;其中之一是#!void
,它等效于计算表达式的结果(values)
(这意味着“根本没有值”)。如果您#!void
从 read-eval-print 循环中获取值,它将不会打印:
#|kawa:1|# #!void
#|kawa:2|# (values)
#|kawa:3|#
这是因为 Kawa 的 read-eval-print 循环用于display
打印出表达式计算的值,并且display
在给定时将选择不打印任何内容#!void
。
在您比较8
vs行为的实验的特定情况下,(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 值的上下文)时,它可以丢弃返回值(display 7)
并插入#!null
其中.
我为什么这么说?好吧,还有另一种方法可以在 Scheme 中打印出值:您可以使用该write
过程。该display
过程通常用于“人类(或最终用户)可读”输出,而该write
过程旨在显示更多关于给定数据结构的信息(如果可用)。例如:
#|kawa:1|# (display "Hello World")
Hello World
#|kawa:2|# (write "Hello World")
"Hello World"
#|kawa:3|#
在上面,display
扔掉了我有一个字符串的信息,只关注那个字符串的内容,同时write
告诉我“我们这里有一个字符串,它有 11 个字符长。这是它的内容。”
所以,如果我们write
调用的结果会发生什么display
?
#|kawa:1|# (write (display 8))
8#!void
#|kawa:2|#
在这里,我们没有从读取和编译步骤中得到任何警告。相反,它保留了 的值(display 8)
。所以它首先评估(display 8)
(打印8
到输出),然后将产生的值(#!void)
输入到write
调用中。
(我不会声称这是有史以来最清晰的语义。但我的推断是 Kawa 的warn-void-used告诉我们允许编译器插入 a#!null
代替 a 的情况#!void
)