【问题标题】:Clojure asterisks special forms (fn*, let*, etc...)Clojure 星号特殊形式(fn*、let* 等...)
【发布时间】:2015-09-15 21:46:10
【问题描述】:

我发现很多“特殊形式”只是在后台使用星号版本的宏(fn*、let* 和所有其他形式)。

例如,在 fn 的情况下,它将解构功能添加到 fn* 单独不提供的组合中。我试图找到一些关于 fn* 可以自己做什么和不能做什么的详细文档,但我没有那么幸运。

绝对支持:

  • &/catchall 指标

    (fn* [x & rest] (do-smth-here...))
    
  • 奇怪的是,arity 的重载也是如此:

    (fn* ([x] (smth-with-one-arg ...) 
         ([x y] (smth-with-two-args ...))
    

所以我最后的问题是,为什么不只定义:

(fn& [& all-args] ...)

这绝对是最小的并且可以通过宏提供所有的arity选择(检查参数列表的大小、if/case语句以直接代码路径、将前几个参数绑定到所需的符号等)。

这是出于性能原因吗?也许有人甚至有一个链接到星号特殊形式的实际标准定义。

【问题讨论】:

  • 您的意思是上面的“(fn* ...”而不是“(fn& ...”)?
  • 为清楚起见,fn& 由我最初编写的 fn* 的编辑更改。在这种情况下,fn& 表示 fn* 的一个非常小的版本。然而,这个版本并不存在,只是一个假设的结构。

标签: function clojure macros special-form


【解决方案1】:

Arity 选择利用 JVM 虚拟方法调度:each arity (from 0 to 20 arguments) has its own method,并且有一个用于 21+-arg arities 的方法。

您可能会注意到applyTo 方法,它是类似于您对fn& 提出的通用方法。它的实现只是一个giant switch 来选择正确的专用方法。

【讨论】:

    【解决方案2】:

    是的,您可以在您建议的超原始fn& 之上将所有这些作为宏来完成,而且它肯定会简化编译器的实现。没有这样做的原因部分是出于性能原因(它会相当慢,并且 JVM 已经具有基于 arity 的快速调度工具),部分是“装饰性的”:这意味着函数的每个 arity 都是不同的函数被编译到的 JVM 类的方法,这使得堆栈跟踪更好。这也有助于 JIT 更好地“理解”我们的功能,从而进行相应的优化。

    【讨论】:

    • 我认为这是两个很好的理由。 Clojure 正在使用/适应底层 JVM 架构,以使其在其他领域也受到青睐。所以它可能是一种向编译器/JVM 传达有用信息的方式,否则这些信息会通过宏丢失。
    • 接受,因为它提供了 fn* 为何如此的最佳概述。
    【解决方案3】:

    我的猜测会是这是方便/可扩展性驱动的。编译器(其中 fn* 实际上是“定义”/处理的)是用 java 编写的,并处理最少需要的功能以引导语言,而 fn 是构建在它之上的宏。与其他一些形式相同。 Rich 曾在某处声明,他可以将编译器从 java 重写为 clojure,但没有看到好处(如果错了,请纠正我)。

    【讨论】:

    • fn& 写起来会比fn* 简单得多,而且用fn& 构建fn* 的宏也不是很困难。与 Rich 采用简单的 fn& 方法相比,实际完成事情的方式肯定没有那么方便。
    • 我的想法是,当前的实现不利于便利性和可扩展性,因为对于最简单的情况( fn*) 并让宏处理单个绑定比通过特殊形式直接将一些参数(在 & 休息之前)绑定到“单个”变量更容易。此外,特殊形式 (fn*) 现在必须自己处理 arity 解析,这可能(在我的“建议”中)也可以是库功能(由 fn 提供)。
    猜你喜欢
    • 1970-01-01
    • 2019-12-24
    • 2023-03-30
    • 1970-01-01
    • 2015-11-22
    • 2016-09-10
    • 2019-06-08
    • 2015-09-06
    • 1970-01-01
    相关资源
    最近更新 更多