【问题标题】:Why should I use 'apply' in Clojure?为什么我应该在 Clojure 中使用“应用”?
【发布时间】:2010-11-18 10:14:11
【问题描述】:

这是 Rich Hickey 在其中一篇博文中所说的,但我不明白使用 apply 的动机。请帮忙。

Clojure 和 CL 的一个很大区别在于 Clojure 是 Lisp-1,因此不需要 funcall,而 apply 仅用于将函数应用于运行时定义的参数集合。所以,(apply f [i]) 可以写成 (f i)。

另外,他所说的“Clojure 是 Lisp-1”并且不需要 funcall 是什么意思?我从来没有在 CL 中编程过。

谢谢

【问题讨论】:

标签: lisp clojure


【解决方案1】:

应用类型操作的通常模式是将运行时提供的函数与一组参数组合在一起,同上。

我在 clojure 方面做得还不够,无法确信该特定语言的微妙之处,以判断在这种情况下使用 apply 是否是绝对必要的。

【讨论】:

    【解决方案2】:

    如果要传递给函数的参数数量未知在编译时,您将使用 apply(抱歉,不知道 Clojure 语法好吧,诉诸Scheme):

    (define (call-other-1 func arg) (func arg))
    (define (call-other-2 func arg1 arg2) (func arg1 arg2))
    

    只要在编译时知道参数的数量,就可以直接传递它们,就像上面示例中所做的那样。但是如果在编译时不知道参数的数量,你就不能这样做(好吧,你可以尝试类似的方法):

    (define (call-other-n func . args)
      (case (length args)
        ((0) (other))
        ((1) (other (car args)))
        ((2) (other (car args) (cadr args)))
        ...))
    

    但这很快就会变成一场噩梦。这就是 apply 进入图片的地方:

    (define (call-other-n func . args)
      (apply other args))
    

    它接受列表中包含的任意数量的参数作为最后一个参数,并使用这些值调用作为第一个参数传递给 apply 的函数。

    【讨论】:

      【解决方案3】:

      术语 Lisp-1 和 Lisp-2 指的是函数是否与变量在同一个命名空间中。

      在 Lisp-2(即 2 个命名空间)中,表单中的第一项将被评估为函数名称——即使它实际上是具有函数值的变量的名称。所以如果你想调用一个变量函数,你必须将变量传递给另一个函数。

      在 Lisp-1 中,例如 Scheme 和 Clojure,计算为函数的变量可以放在初始位置,因此您无需使用 apply 将其作为函数进行计算。

      【讨论】:

        【解决方案4】:

        apply 基本上解开一个序列并将函数作为单独的参数应用于它们。

        这是一个例子:

        (apply + [1 2 3 4 5])
        

        返回 15。它基本上扩展为 (+ 1 2 3 4 5),而不是 (+ [1 2 3 4 5])

        【讨论】:

        • 又一个例子:(apply + 1 2 [3 4 5]) => 15
        • ...但这失败了 (apply + 1 2 [3 4 5]),这是出乎意料的。知道为什么吗? (我是 Clojure 的新手)
        • hmmm.. 只需将其输入到 REPL 中,您是对的,它工作正常。一定是和我近两年前建立的环境有关。
        【解决方案5】:

        Apply 对协议很有用,尤其是与线程宏结合使用时。我刚刚发现了这一点。既然你can't use the & macro to expand interface arguments at compile time,你可以应用一个不可预测的大小来代替。

        因此,例如,我将其用作包含有关特定 xml 文件的一些元数据的记录与文件本身之间的接口的一部分。

        (query-tree [this forms]
          (apply xml-> (text-id-to-tree this) forms)))
        

        text-id-to-tree 是此特定记录的另一种方法,它将文件解析为 xml 拉链。在另一个文件中,我使用实现query-tree 的特定查询扩展协议,指定要通过 xml-> 宏线程化的命令链:

        (tags-with-attrs [this]
          (query-tree this [zf/descendants zip/node (fn [node] [(map #(% node) [:tag :attrs])])])
        

        (注意:这个查询本身会为没有标签的标签返回很多“nil”结果 属性。过滤和减少一个干净的唯一值列表)。

        顺便说一下,zf 指的是 clojure.contrib.zip-filter,而 zip 指的是 clojure.zip。 xml-> 宏来自 clojure.contrib.zip-filter.xml 库,我:use

        【讨论】:

          【解决方案6】:

          您使用apply 将处理多个参数的函数转换为处理单个参数序列的函数。您还可以在序列之前插入参数。例如,map 可以处理多个序列。此示例(来自ClojureDocs)使用map 转置矩阵。

          user=> (apply map vector [[:a :b] [:c :d]])
          ([:a :c] [:b :d])
          

          这里插入的一个参数是vector。所以apply 扩展为

          user=> (map vector [:a :b] [:c :d])
          

          可爱!

          PS 要返回向量的向量而不是向量序列,请将整个内容包装在 vec 中:

          user=> (vec (apply map vector [[:a :b] [:c :d]]))
          

          当我们在这里时,vec 可以定义为 (partial apply vector),尽管它不是。

          关于 Lisp-1 和 Lisp-2:1 和 2 表示一个名称在给定上下文中可以表示的事物的数量。在 Lisp-2 中,你可以有两个不同的东西(一个函数和一个变量)同名。因此,无论任何一个可能有效,您都需要用一些东西来装饰您的程序以表明您的意思。值得庆幸的是,Clojure(或 Scheme ...)允许名称仅表示一件事,因此不需要这样的修饰。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-08-14
            • 2017-07-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-08-07
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多