在 Clojure 中传递方法名称进行评估的惯用方法?

发布于 2024-09-13 11:04:36 字数 284 浏览 8 评论 0原文

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

(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 中是否有更惯用的方法来做到这一点?

I'm passing the name of a function for use in another method.

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

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

Is there a more idiomatic way to do this in clojure?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

橘味果▽酱 2024-09-20 11:04:36
  • 更喜欢向量而不是列表。大多数时候您不必引用向量,并且它对于很多事情(例如随机访问)具有更好的性能。与其他 Lisp 相比,Clojure 中使用列表的情况要少得多。
  • 优先使用关键字而不是带引号的符号。关键字以“常量字符串”或枚举值的形式出现。 Clojure 中的关键字可以属于一个名称空间,因此它们具有符号的所有优点。再说一次,不需要引用关键字,这很好。除非您正在编写宏,否则在 Clojure 中很少使用带引号的符号。
  • #'first 是名为“first”的 var; first 是名为“first”的 var 的值,即 fn。在这种情况下 (#'first foo)(first foo) 给出相同的答案,但 #'first 每次都会进行额外的取消引用你称之为。因此,除非您希望反复发生取消引用,否则不要这样做。通常不需要使用#'
  • 内置的 map 是惰性的,而你的则不然。内置的 map 利用分块 seq 来获得更好的性能,而您的则不然。惯用的代码不必是懒惰的或使用分块的 seq,但请记住,内置函数具有一些这种魔力。所以好好利用一下。
  • 空 seq 的惯用测试不是 (= '() x),而是 (seq x),如果 则返回 nil >x 为空。请注意,在 Clojure 中,(= '() nil) 为 false。
  • 如果您确实需要使用空列表(您应该很少需要这样做),则不必引用它。只需使用 () 即可。
  • 内置 map 首先接受函数参数,因为它接受多个集合参数。当函数接受多个参数时,这些参数必须位于参数列表的最后。我认为另一种读法也更好:“(map f coll):在这个集合中映射这个函数”。
  • 如果您只有两个选项,则无需使用 cond。您可以使用 if 来代替。如果 if 中的某个分支返回 nil,则可以使用 when。在适当的时候使用 whenif 很好,因为它们会立即向读者表明你的意图,而 cond 可以做任何事情并强迫读者阅读更多内容。

Rafał Dowgird 的版本是惯用的,只是我会颠倒论证的顺序。我会这样称呼它:

user> (mapper first [[:blue :red] [:green :red] [:white :red]])
(:blue :green :white)
  • Prefer vectors to lists. You don't have to quote a vector most of the time, and it has better performance for a lot of things, like random access. Lists are used much more rarely in Clojure than in other Lisps.
  • Prefer keywords to quoted symbols. Keywords stand out as "constant strings" or enumerated values. Keywords in Clojure can belong to a namespace, so they have all the advantages of symbols. And again, there's no need to quote keywords, which is nice. Quoted symbols are used pretty rarely in Clojure, unless you're writing macros.
  • #'first is the var called "first"; first is the value of the var called "first", i.e. the fn. In this case (#'first foo) and (first foo) give the same answer, but #'first does an extra dereference every time you call it. So don't do this unless you want that dereference to happen over and over. There's usually no need to use #'.
  • The built-in map is lazy, whereas yours isn't. The built-in map takes advantage of chunked seqs for better performance, whereas yours doesn't. Idiomatic code doesn't have to be lazy or use chunked seqs, but keep in mind that the builtins have some of this magic going on. So it's good to take advantage.
  • Rather than (= '() x), the idiomatic test for an empty seq is (seq x), which returns nil if x is empty. Note that in Clojure, (= '() nil) is false.
  • If you do ever need to use the empty list (which you should rarely need to do), you don't have to quote it. Just use ().
  • Built-in map takes the function argument first because it accepts multiple collection arguments. When a function takes multiple arguments, those arguments have to go last in the argument list. I think it reads better the other way too: "(map f coll): map this function across this collection".
  • There's no need to use cond if you only have two options. You can use if instead. And if one of the branches in your if returns nil, you can use when. It's nice to use when and if when appropriate, because they signal your intentions to the reader immediately, whereas cond could do anything and forces the reader to read more.

Rafał Dowgird's version is idiomatic, except I'd flip the order of arguments around. And I'd call it like this:

user> (mapper first [[:blue :red] [:green :red] [:white :red]])
(:blue :green :white)
断肠人 2024-09-20 11:04:36

我相信你已经明白了这句话大部分是惯用的。 Clojure 自己的 map 使用:

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

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

coll 和 f 是分别表示集合和函数的惯用参数名称。

I believe you got it mostly idiomatic. Clojure's own map uses:

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

I have shortened it severely - the original produces a lazy sequence, deals with multiple collections, chunked-seqs, etc. By the way - I assume you want to pass the actual function, not it's name.

The coll and f are idiomatic arg names to represent collections and functions, respectively.

自由如风 2024-09-20 11:04:36

你的版本对我来说看起来不错。您在 clojure 代码库中看到的常用名称是“coll”,表示集合。我还见过“xs”,我认为这是 Haskell 风格。您还可以参考 Clojure 库编码标准 关于各种约定。

回到这个例子:两个观察结果。

  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”而不是第一个/其余习惯用法。 'next' 返回第一个元素之后的元素序列。因此,可以通过对集合进行 seq'ing 来检查空性,对于空列表或空向量将返回 nil。这样它适用于所有集合。

Your version looks good to me. The usual names you will see in the clojure code base is 'coll' for collections. I have also seen 'xs' which is the Haskell style, I think. You may also refer to the Clojure library coding standards on various conventions.

Coming back to the example: Two observations.

  1. Use :else for 'cond' as the escape condition, instead of the Common Lisp style 'T'.
  2. Instead of assuming lists, think sequences.

With these two in mind, if I rewrite your code:

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)

Note that conj does the "right thing" as far as collections are concerned. It adds the new element to the head of a list, to the tail of a vector and so on. Also note the use of 'next' instead of the first/rest idioms in traditional lisp. 'next' returns a sequence of elements after the first element. So, empty-ness can be checked by seq'ing on the collection which will return nil for an empty list or an empty vector. This way it works for all collections.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文