【问题标题】:Idiomatic way to pass a method name for evaluation in Clojure?在 Clojure 中传递方法名称以进行评估的惯用方式?
【发布时间】:2011-03-28 07:52:14
【问题描述】:

我正在传递一个函数的名称,以便在另一个方法中使用。

(defn mapper [m function]
  (cond
   (= '() m) '()
   true (cons (function (first m))
            (mapper (rest m) function))))

(println (mapper '((blue red)(green red)(white red)) #'first))

在 clojure 中有没有更惯用的方法来做到这一点?

【问题讨论】:

  • 我猜惯用的方法是使用map函数:(map first '((blue red) (green red) (white red)))

标签: function hash clojure lisp idioms


【解决方案1】:
  • 更喜欢向量而不是列表。大多数时候你不必引用向量,它在很多事情上都有更好的性能,比如随机访问。列表在 Clojure 中的使用比在其他 Lisp 中少得多。
  • 优先使用关键字而不是引用符号。关键字作为“常量字符串”或枚举值脱颖而出。 Clojure 中的关键字可以属于一个命名空间,因此它们具有符号的所有优点。再一次,没有必要引用关键字,这很好。 Clojure 中很少使用带引号的符号,除非您正在编写宏。
  • #'first 是名为“first”的变量; first 是名为“first”的 var 的值,即 fn。在这种情况下,(#'first foo)(first foo) 给出了相同的答案,但 #'first 每次调用它时都会进行额外的取消引用。所以不要这样做,除非你希望这种取消引用一遍又一遍地发生。通常不需要使用#'
  • 内置的map 是惰性的,而你的不是。内置的map 利用分块序列来获得更好的性能,而您的则没有。惯用代码不必是懒惰的或使用分块的 seq,但请记住,内置程序具有一些这种魔力。所以好好利用一下。
  • 而不是(= '() x),空seq 的惯用测试是(seq x),如果x 为空,则返回nil。请注意,在 Clojure 中,(= '() nil) 为 false。
  • 如果您确实需要使用空列表(您应该很少需要这样做),您不必引用它。只需使用()
  • 内置map 首先接受函数参数,因为它接受多个集合参数。当一个函数接受多个参数时,这些参数必须在参数列表中排在最后。我认为换一种方式读起来也更好:“(map f coll): map this function across this collection”。
  • 如果您只有两个选项,则无需使用cond。您可以改用if。如果你的if 中的一个分支返回nil,你可以使用when。在适当的时候使用whenif 很好,因为它们会立即向读者表明你的意图,而cond 可以做任何事情并迫使读者阅读更多内容。

Rafał Dowgird 的版本是惯用的,除了我会颠倒参数的顺序。我会这样称呼它:

user> (mapper first [[:blue :red] [:green :red] [:white :red]])
(:blue :green :white)

【讨论】:

  • 同意翻转论点。或者 (defn mapper [coll f & args] ...)args 作为附加参数传递给 f 以便像 (mapper [5 2 3] + 1) 这样的事情是可能的。
【解决方案2】:

我相信你得到它主要是惯用的。 Clojure 自己的map 使用:

(defn mapper [coll f]
 (when-let [s (seq coll)]
    (cons (f (first s)) (mapper (rest s) f))))

我已经大大缩短了它 - 原始产生一个惰性序列,处理多个集合、分块序列等。顺便说一下 - 我假设你想传递实际的函数,而不是它的 名称.

collf 是惯用的 arg 名称,分别表示集合和函数。

【讨论】:

    【解决方案3】:

    我觉得你的版本不错。您将在 clojure 代码库中看到的常用名称是集合的“coll”。我认为,我还看到了 Haskell 风格的“xs”。您也可以参考Clojure library coding standards 了解各种约定。

    回到示例:两个观察结果。

    1. 使用 :else 作为“cond”的转义条件,而不是 Common Lisp 样式的“T”。
    2. 不要假设列表,而要考虑序列。

    考虑到这两点,如果我重写你的代码:

    user> (defn mapper [coll f]
            (cond
              (not (seq coll)) nil
              :else (conj (mapper (next coll) f)
                          (f (first coll)))))
    #'user/mapper
    user> (mapper '(1 2 3) #(* % %))
    (1 4 9)
    user> (mapper [1 2 3] #(* % %))
    (1 4 9)
    

    请注意,就集合而言,conj 做了“正确的事情”。它将新元素添加到列表的头部,向量的尾部等等。还要注意在传统的 lisp 中使用 'next' 而不是 first/rest idioms。 'next' 返回第一个元素之后的元素序列。因此,可以通过对集合进行 seq'ing 来检查空性,该集合将为空列表或空向量返回 nil。这种方式适用于所有集合。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-03
      • 1970-01-01
      • 2012-03-02
      • 1970-01-01
      • 2020-03-05
      • 1970-01-01
      • 2021-07-04
      相关资源
      最近更新 更多