使用短符号 #(..) 的匿名函数有一些我不太明白的地方。
以下方法可行:
REPL> ((fn [s] s) "Eh")
"Eh"
但这个不行:
REPL> (#(%) "Eh")
这个可以正常工作:
REPL> (#(str %) "Eh")
"Eh"
我不理解的是为什么 (#(%) "Eh") 不起作用,同时我在((fn [s] s) "Eh")中不需要使用 str。
它们都是匿名函数,且在这里都只有一个参数。为什么简写符号需要一个函数而另一种符号却不需要?
使用短符号 #(..) 的匿名函数有一些我不太明白的地方。
以下方法可行:
REPL> ((fn [s] s) "Eh")
"Eh"
但这个不行:
REPL> (#(%) "Eh")
这个可以正常工作:
REPL> (#(str %) "Eh")
"Eh"
我不理解的是为什么 (#(%) "Eh") 不起作用,同时我在((fn [s] s) "Eh")中不需要使用 str。
它们都是匿名函数,且在这里都只有一个参数。为什么简写符号需要一个函数而另一种符号却不需要?
#(...)
是...的简写
(fn [arg1 arg2 ...] (...))
(argN的数量取决于正文中有多少%N)。因此,当您编写以下内容时:
#(%)
它被翻译成:
(fn [arg1] (arg1))
请注意,这与您的第一个匿名函数不同,它看起来像:
(fn [arg1] arg1)
你的版本返回arg1作为一个值,而从缩写扩展得到的版本则尝试将其作为函数调用。由于字符串不是有效的函数,因此会出现错误。
由于缩写提供了一组括号,围绕在函数体周围,它只能用于执行单个函数调用或特殊形式。
正如其他答案已经非常好地指出的那样,您发布的#(%)
实际上会扩展成类似于(fn [arg1] (arg1))
的内容,这与(fn [arg1] arg1)
并不一样。
@John Flatness指出您可以直接使用identity
,但如果您正在寻找一种使用#(...)
分派宏编写identity
的方法,可以这样做:
#(-> %)
通过将#(...)
调度宏与->
线程宏结合使用,它会扩展为类似于(fn [arg1] (-> arg1))
的东西,然后再扩展为(fn [arg1] arg1)
,这正是您想要的。我还发现->
和#(...)
宏组合对编写返回向量的简单函数非常有帮助,例如:
#(-> [%2 %1])
当你使用 #(...)
时,可以想象你实际上是在写 (fn [args] (...))
,包括井号后面的括号。
因此,你的无效示例将转换为:
((fn [s] (s)) "Eh")
很显然,这种方式不起作用,因为您试图“调用”字符串“Eh”。使用str
的示例有效,因为现在您的函数是(str s)
而不是(s)
。(identity s)
将更接近于您的第一个示例,因为它不会强制转换为字符串。
如果你想一下,这很有道理,因为除了这个完全最小的例子之外,每个匿名函数都要调用“某些东西”,所以要求另一组嵌套的圆括号来实际进行调用可能有点愚蠢。
如果您不确定匿名函数被转换为什么,可以使用macroexpand
过程获取其表示形式。在将表达式传递给macroexpand之前,请记得引用它。在这种情况下,我们可以这样做:
(macroexpand '#(%))
# => (fn* [p1__281#] (p1__281#))
这可能会打印出不同的名称,如p1__281#
,它们是变量%
的表示。
您还可以macroexpand
完整调用。
(macroexpand '(#(%) "Eh"))
# => ((fn* [p1__331#] (p1__331#)) "Eh")
通过使用短名称替换晦涩的变量名,使其更易读。我们得到了被接受答案所报告的内容。
# => ((fn* [s] (s)) "Eh")
资源: