(display 8) 的 return 类型是什么? / 什么是空值表达式?

What is the return type of (display 8) ? / What are void-valued expressions?

在 Scheme 的 Kawa 实现中,表达式

(null? ())

显然 returns

#t .

但是如果我输入

(null? (display 8))

进入解释器,输出为

8#f

所以似乎 display 是一个 确实 有副作用的函数,即打印值和某种非空 return 值。唔。也许 (display 8) returns 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) 不是空值而是 "void-valued"?那是什么意思?我可以检查 void-valued 就像我可以在 Scheme 中检查 null 吗?

另外,为什么错误信息后面会出现8

您的推断是正确的,display 是一个 return 值的函数(除了具有打印到当前输出端口的副作用)。然而,这个特定调用 display returns 的值是读取-求值-打印循环简单地选择不打印的值,当它作为表达式求值的结果自行发生时。

Kawa有很多特别的constants;其中之一是 #!void,它等效于计算表达式 (values) 的结果(这意味着 "no values at all")。如果您从 read-eval-print 循环中得到值 #!void,它不会打印:

#|kawa:1|# #!void
#|kawa:2|# (values)
#|kawa:3|# 

这是因为 Kawa 的 read-eval-print 循环 uses display 打印出表达式求值的值,而 display 在给定 #!void 时会选择不打印任何内容。


在您比较 8(display 8) 行为的具体实验案例中,实际发生的情况存在重大差异。当您向解释器提供任何输入时,它:

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

所以当你喂它时 8,打印发生在第 3 步。当你喂它 (display 8),打印发生在第 2 步,然后从第 3 步开始打印什么都不打印(因为 (display 8) 编辑的值 return 是解释器选择不打印的值)。

观察这种区别的一种方法:根据感兴趣的表达式构建一个列表。

#|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 解释器在读取和编译步骤中足够聪明(发生在之前 评估和打印步骤)来分析潜在问题的代码。在这里,它表示 "The result of invoking display is not meant to be used as if it were a normal value"(与数字或字符串相比)。

所以这就解释了为什么你会看到一条错误消息(因为它认为调用 display 的结果是无效值),并且它知道对这些值的处理可能不符合用户的期望。 (它也解释了为什么你的例子中的数字 8 在 错误消息之后被打印 :因为错误消息是在 "read" 步骤中生成的,但是显示如上所述,发生在 "evaluation" 步骤中。


为什么说"the treatment of such values may not match the user's expectations"?那么,从上面我们 运行 (list (display 7) 8 (display 9)) 的实验中,您 可能 推断出评估 (display 7) 的结果是 #!null。但事实并非如此!

在Kawa中,#!null是一个特殊的常量,与#!void不同。出于某种原因,Kawa 解释器决定当您将 (display 7) 插入列表构造表达式(或者更一般地说,我认为任何期望非空值的上下文)时,它可以丢弃 return (display 7) 的值并在其中插入 #!null

我为什么这么说?那么,在 Scheme 中还有另一种打印值的方法:您可以使用 write 过程。 display 过程通常用于 "human (or end-user) readable" 输出,而 write 过程用于显示有关给定数据结构的更多信息(如果可用)。例如:

#|kawa:1|# (display "Hello World")
Hello World
#|kawa:2|# (write "Hello World")
"Hello World"
#|kawa:3|# 

上面display丢掉了我有一个字符串的信息,只关注那个字符串的内容,而write告诉我"we have a string here, it is 11 characters long. Here are its contents."

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)

的位置

根据报告,display 的 return 值未定义。实际上,这意味着可能会有一个实现,其中 (= 8 (display 8)) 的计算结果为 #t,但您不能依赖它。

大多数实现按字面意思采用 "undefined" 并创建一个值,该值代表未定义的内容,就像 #f 是系统中的一个错误值一样。该值通常不是数字,因此使用要求所有参数均为数字的 = 将失败,这就是产生错误消息的原因,但是 (eqv? 8 (display 8)) ; ==> #f since display return除了 8eqv? 之外的其他东西可以比较任何值,包括 void 值。

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

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

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