匿名函数简写

85

使用短符号 #(..) 的匿名函数有一些我不太明白的地方。

以下方法可行:

REPL>  ((fn [s] s) "Eh")
"Eh"

但这个不行:

REPL>  (#(%) "Eh")

这个可以正常工作:

REPL> (#(str %) "Eh")
"Eh"

我不理解的是为什么 (#(%) "Eh") 不起作用,同时我在((fn [s] s) "Eh")中不需要使用 str

它们都是匿名函数,且在这里都只有一个参数。为什么简写符号需要一个函数而另一种符号却不需要?

4个回答

128
#(...)

是...的简写

(fn [arg1 arg2 ...] (...))

(argN的数量取决于正文中有多少%N)。因此,当您编写以下内容时:

#(%)

它被翻译成:

(fn [arg1] (arg1))

请注意,这与您的第一个匿名函数不同,它看起来像:

(fn [arg1] arg1)

你的版本返回arg1作为一个值,而从缩写扩展得到的版本则尝试将其作为函数调用。由于字符串不是有效的函数,因此会出现错误。

由于缩写提供了一组括号,围绕在函数体周围,它只能用于执行单个函数调用或特殊形式。


65

正如其他答案已经非常好地指出的那样,您发布的#(%)实际上会扩展成类似于(fn [arg1] (arg1))的内容,这与(fn [arg1] arg1)并不一样。

@John Flatness指出您可以直接使用identity,但如果您正在寻找一种使用#(...)分派宏编写identity的方法,可以这样做:

#(-> %)

通过将#(...)调度宏与->线程宏结合使用,它会扩展为类似于(fn [arg1] (-> arg1))的东西,然后再扩展为(fn [arg1] arg1),这正是您想要的。我还发现->#(...)宏组合对编写返回向量的简单函数非常有帮助,例如:

#(-> [%2 %1])

21

当你使用 #(...) 时,可以想象你实际上是在写 (fn [args] (...))包括井号后面的括号。

因此,你的无效示例将转换为:

((fn [s] (s)) "Eh")

很显然,这种方式不起作用,因为您试图“调用”字符串“Eh”。使用str的示例有效,因为现在您的函数是(str s)而不是(s)(identity s)将更接近于您的第一个示例,因为它不会强制转换为字符串。

如果你想一下,这很有道理,因为除了这个完全最小的例子之外,每个匿名函数都要调用“某些东西”,所以要求另一组嵌套的圆括号来实际进行调用可能有点愚蠢。


0

如果您不确定匿名函数被转换为什么,可以使用macroexpand过程获取其表示形式。在将表达式传递给macroexpand之前,请记得引用它。在这种情况下,我们可以这样做:

(macroexpand '#(%))
# => (fn* [p1__281#] (p1__281#))

这可能会打印出不同的名称,如p1__281#,它们是变量%的表示。

您还可以macroexpand完整调用。

(macroexpand '(#(%) "Eh"))
# => ((fn* [p1__331#] (p1__331#)) "Eh")

通过使用短名称替换晦涩的变量名,使其更易读。我们得到了被接受答案所报告的内容。

# => ((fn* [s] (s)) "Eh")

资源:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接