什么是(显示8)的返回类型? /什么是空值表达式?

时间:2017-06-14 09:27:14

标签: types scheme lisp kawa

在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出现在错误消息之后?

2 个答案:

答案 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)的行为的具体情况中,实际情况确实存在重大差异。当你向解释器提供任何输入时,它:

  1. 读取(并编译)输入
  2. 将已编译的表达式计算为值,并
  3. 打印出结果值。
  4. 因此当您输入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)插入到列表构造表达式中时(或者更一般地说,我认为任何上下文期望非空值),它可以抛弃返回值(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)

我更倾向于使用标准来定义返回是参数,因为它可能已被用于某些东西。毕竟输入是堆栈上的内容,所以我想这不会更加努力。