【问题标题】:What is the return type of (display 8) ? / What are void-valued expressions?(display 8) 的返回类型是什么? / 什么是空值表达式?
【发布时间】:2017-06-14 09:27:14
【问题描述】:

在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 中检查 null 一样检查 void-valued 吗?

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

【问题讨论】:

    标签: types scheme lisp kawa


    【解决方案1】:

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

    卡瓦有多个特别的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) 行为的实验的特定情况下,实际上发生的事情存在重大差异。当您向解释器提供任何输入时,它:

    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,然后构建了一个包含三个元素的列表:#!null、8,然后又是#!null。


    Kawa 解释器还警告我们,我们的代码似乎存在问题:Kawa 解释器在读取和编译步骤(发生在评估和打印步骤之前)足够聪明,可以分析潜在问题的代码。在这里,它说“调用display 的结果并不意味着像正常值一样使用”(与数字或字符串相比)。

    所以这解释了为什么您会看到一条错误消息(因为它认为调用 display 的结果是无效值),并且它知道对这些值的处理可能与用户的期望不符。 (它还解释了为什么您的示例中的数字8 打印在错误消息之后:因为错误消息是在“读取”步骤期间生成的,但显示发生在“评估”步骤期间,如上所述。


    为什么我会说“对这些值的处理可能不符合用户的期望”?好吧,从上面我们运行(list (display 7) 8 (display 9)) 的实验中,您可能推断出评估(display 7) 的结果是#!null。但事实并非如此!

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

    【讨论】:

    • 能否以某种方式检查 void like in racket?表达式(void? (display 8)) 不起作用。
    • 我不确定。如果您可以说服解释器绑定 (display 8) 的实际返回值而不触发 void-valued 表达式警告,那么您应该可以使用 (equal? #!void bound-variable)。
    • 但问题是 Kawa 似乎非常激进地使用了空值表达式警告。例如,它甚至对 this 代码发出警告:(call-with-values (lambda () (display 8)) (lambda l (write l))),这对我来说似乎是错误的,可能是一个实际的错误。
    【解决方案2】:

    根据报告,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)
    

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

    【讨论】:

      猜你喜欢
      • 2018-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-04
      • 2022-06-16
      • 2018-01-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多