(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)
行为的具体实验案例中,实际发生的情况存在重大差异。当您向解释器提供任何输入时,它:
- 读取(并编译)输入,
- 将编译后的表达式计算为一个值,并且
- 打印出结果值。
所以当你喂它时 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
,然后构建了一个包含三个元素的列表:#!null
、8
,然后再次 #!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除了 8
和 eqv?
之外的其他东西可以比较任何值,包括 void 值。
查看 return 的内容的一个好方法是评估 (list (display 8))
。实现的 REPL 会抑制未定义的值,但它肯定不会抑制与第一个元素具有相同值的列表。
(list (display 8)) ; ==> (#!void) (and prints 8 on the terminal as side effect)
我更喜欢标准来定义 return 是参数,因为从那时起它就可以用于某些事情。毕竟输入是堆栈上的内容,所以我想这样做不会花费更多精力。
在 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)
行为的具体实验案例中,实际发生的情况存在重大差异。当您向解释器提供任何输入时,它:
- 读取(并编译)输入,
- 将编译后的表达式计算为一个值,并且
- 打印出结果值。
所以当你喂它时 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
,然后构建了一个包含三个元素的列表:#!null
、8
,然后再次 #!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除了 8
和 eqv?
之外的其他东西可以比较任何值,包括 void 值。
查看 return 的内容的一个好方法是评估 (list (display 8))
。实现的 REPL 会抑制未定义的值,但它肯定不会抑制与第一个元素具有相同值的列表。
(list (display 8)) ; ==> (#!void) (and prints 8 on the terminal as side effect)
我更喜欢标准来定义 return 是参数,因为从那时起它就可以用于某些事情。毕竟输入是堆栈上的内容,所以我想这样做不会花费更多精力。