在方案的卡瓦实现中,表达式
(null? ())
明显的回报
#t。
但如果我打字
(null? (display 8))
在解释器中,输出是
8#f
因此,似乎display是一个具有副作用的函数,即打印值和某种非空返回值。嗯。也许(display 8)会返回8?毕竟,8和(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)不是空值,而是“空值”?那是什么意思?我能检查空值,就像我可以空值一样吗?
另外,为什么8出现在错误消息之后?
发布于 2017-06-14 10:07:07
您的推论是正确的,即display是一个返回值的函数(除了打印到当前输出端口的副作用之外)。然而,这个对display的特定调用返回的值,是读-古-打印循环选择不打印的结果,当它单独发生作为一个表达式的结果。
Kawa有许多特殊的常量;其中一个是#!void,它相当于计算表达式(values)的结果(意思是“根本没有值”)。如果从read循环中获得值#!void,它将不会打印:
#|kawa:1|# #!void
#|kawa:2|# (values)
#|kawa:3|# 这是因为Kawa的读- value循环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:因为错误消息是在"read“步骤中生成的,但是显示发生在”计算“步骤中,如前所述。
为什么我要说“这些值的处理可能不符合用户的期望”?从上面我们运行(list (display 7) 8 (display 9))的实验中,您可以推断出(display 7)的评估结果是#!null。但这其实不是真的!
在Kawa中,#!null是一个特殊的常数,它不同于#!void。出于某种原因,Kawa解释器决定,当您将(display 7)插入列表构造表达式(或者更广泛地说,我认为任何上下文期望一个非空值)时,它可以丢弃(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的警告-无效-使用正在告诉我们编译器可以插入#!null而不是#!void的情况)
发布于 2017-06-14 23:07:45
根据报告,display的返回值是未定义的。实际上,这意味着可能有一个(= 8 (display 8))计算为#t的实现,但您不能依赖它。
大多数实现从字面上看都是“未定义的”,并以与#f相同的方式表示未定义的东西的值,这与系统中的一个虚值相同。这个值通常不是一个数字,所以使用= (要求所有参数都是数字)将失败,这就是产生错误消息的原因,但是(eqv? 8 (display 8)) ; ==> #f,因为display返回8和eqv?以外的其他值,可以比较任何值,包括空值。
查看它返回的结果的一个很好的方法是评估(list (display 8))。实现的REPL将抑制未定义的值,但它肯定不会抑制具有与第一个元素相同值的列表。
(list (display 8)) ; ==> (#!void) (and prints 8 on the terminal as side effect)我更倾向于用标准来定义返回是一个论点,因为从那时起,它可以被用来做一些事情。毕竟,输入是堆栈开始的内容,所以我想这样做不会需要更多的努力。
https://stackoverflow.com/questions/44540712
复制相似问题