在Scheme的Kawa实现中,表达式
(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)
不是null,而是“无效值”?那是什么意思?我可以在Scheme中检查 void-valued ,就像我可以 null 一样吗?
另外,为什么8
出现在错误消息之后?
答案 0 :(得分:2)
您的推断是正确的display
是一个返回值的函数(除了具有打印到当前输出端口的副作用)。然而,这个display
特定调用返回的值是read-eval-print循环在选择表达式时自行选择不打印的值。
Kawa有一些特殊的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)
插入到列表构造表达式中时(或者更一般地说,我认为任何上下文期望非空值),它可以抛弃返回值(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字符长。这是它的内容。“
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
)
答案 1 :(得分:1)
根据报告,display
的返回值未定义。实际上,这意味着可能存在(= 8 (display 8))
评估为#t
的实现,但您不能依赖它。
大多数实现从字面上理解为“未定义”,并且以与#f
相同的方式创建一个未定义的值的值,这是系统中的一个错误值。此值通常不是数字,因此使用=
要求所有参数都是数字将失败,这就是产生错误消息的原因,但是(eqv? 8 (display 8)) ; ==> #f
因为display
返回的内容不是8
{1}}和eqv?
可以比较任何值,包括空值。
查看返回内容的好方法是评估(list (display 8))
。实现的REPL将抑制未定义的值,但它肯定不会抑制具有与第一个元素相同值的列表。
(list (display 8)) ; ==> (#!void) (and prints 8 on the terminal as side effect)
我更倾向于使用标准来定义返回是参数,因为它可能已被用于某些东西。毕竟输入是堆栈上的内容,所以我想这不会更加努力。