0

在 Scheme 的Kawa实现中,表达式

(null? ())

显然返回

#t.

但是如果我输入

(null? (display 8))

进入解释器,输出是

8#f

所以它似乎是一个确实有副作用display的函数,即打印值和某种非空返回值。唔。也许回报?毕竟,两者都在交互式解释器中显示。(display 8)88(display 8)8

所以我输入

(= 8 (display 8))

回应是

/dev/stdin:5:6: warning - void-valued expression where value is needed
java.lang.NullPointerException
    at gnu.math.IntNum.compare(IntNum.java:181)
    at atInteractiveLevel-5.run(stdin:5)
    at gnu.expr.ModuleExp.evalModule2(ModuleExp.java:293)
    at gnu.expr.ModuleExp.evalModule(ModuleExp.java:212)
    at kawa.Shell.run(Shell.java:283)
    at kawa.Shell.run(Shell.java:196)
    at kawa.Shell.run(Shell.java:183)
    at kawa.repl.processArgs(repl.java:714)
    at kawa.repl.main(repl.java:820)
8

那么,(display 8)不是 null 而是“无效值”?这意味着什么?我可以像在 Scheme 中一样检查void-valued吗?

另外,为什么8在错误消息之后出现?

4

2 回答 2

2

您的推断是正确的,它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


在您比较8vs行为的实验的特定情况下,(display 8)实际上发生的事情有一个至关重要的区别。当您向解释器提供任何输入时,它:

  1. 读取(并编译)输入,
  2. 将编译后的表达式计算为一个值,并且
  3. 打印出结果值。

因此,当您输入它时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,然后构建了三个元素的列表:#!null8,然后#!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

于 2017-06-14T10:07:07.883 回答
1

根据报告,返回值display未定义。实际上,这意味着可能有一个(= 8 (display 8))评估为的实现#t,但您不能依赖它。

#f大多数实现从字面上理解“未定义”,并以与系统中的一个错误值相同的方式生成一个值,该值是它们对未定义事物的表示。该值通常不是数字,因此使用=要求所有参数都是数字的将失败,这就是产生错误消息的原因,但是(eqv? 8 (display 8)) ; ==> #f因为display返回的内容不是8并且eqv?可以比较任何值,包括 void 值。

查看它返回什么的一个好方法是评估(list (display 8)). 实现的 REPL 会抑制未定义的值,但它肯定不会抑制与第一个元素具有相同值的列表。

(list (display 8)) ; ==> (#!void) (and prints 8 on the terminal as side effect)

我更喜欢标准来定义返回是参数,因为那时它可以用于某些事情。毕竟输入是堆栈上的内容,所以我想这样做不会有更多的努力。

于 2017-06-14T23:07:45.443 回答