clojure 是否具有 C# 等效的 yield?

Ami*_*rge 5 clojure

我正在阅读电子书《Scala 和 Clojure 中的函数式编程模式》,并找到了导致此问题的代码示例。

这段代码旨在比较两个 Person 对象。比较算法是 - 首先比较它们的 FName,如果相等则比较它们的 LName,如果相等则比较它们的 MName。

书中给出的 Clojure 代码(或多或少)

(def person1 {:fname "John" :mname "Q" :lname "Doe"})
(def person2 {:fname "Jane" :mname "P" :lname "Doe"})

(defn fname-compare [p1 p2] 
  (do 
    (println "Comparing fname")
    (compare (:fname p1) (:fname p2))))

(defn lname-compare [p1 p2] 
  (do 
    (println "Comparing lname")
    (compare (:lname p1) (:lname p2))))

(defn mname-compare [p1 p2] 
  (do 
    (println "Comparing mname")
    (compare (:mname p1) (:mname p2))))

(defn make-composed-comparison [& comparisons] 
  (fn [p1 p2]
    (let [results (for [comparison comparisons] (comparison p1 p2)) 
          first-non-zero-result 
            (some (fn [result] (if (not (= 0 result)) result nil)) results)] 
      (if (nil? first-non-zero-result)
        0
        first-non-zero-result))))

(def people-comparision-1 
  (make-composed-comparison fname-compare lname-compare mname-compare))

(people-comparision-1 person1 person2)

;Output
;Comparing fname
;Comparing lname
;Comparing mname
;14
Run Code Online (Sandbox Code Playgroud)

事情是,根据这个示例,即使第一个返回非零,它也会进行所有三个比较。在这种情况下,这不是问题。但是,如果我编写了惯用的 C# 代码,那么该代码将只进行一次比较并退出。示例 C# 代码

public class Person {
  public string FName {get; set;}
  public string LName {get; set;}
  public string MName {get; set;}
}

var comparators = 
  new List<Func<Person, Person, int>> {
    (p1, p1) => {
      Console.WriteLine("Comparing FName");
      return string.Compare(p1.FName, p2.FName);
    },
    (p1, p1) => {
      Console.WriteLine("Comparing LName");
      return string.Compare(p1.LName, p2.LName);
    },
    (p1, p1) => {
      Console.WriteLine("Comparing MName");
      return string.Compare(p1.MName, p2.MName);
    }
  };

var p1 = new Person {FName = "John", MName = "Q", LName = "Doe"};
var p2 = new Person {FName = "Jane", MName = "P", LName = "Doe"};

var result = 
  comparators
    .Select(x => x(p1, p2))
    .Where(x => x != 0)
    .FirstOrDefault();

Console.WriteLine(result);

// Output
// Comparing FName
// 1
Run Code Online (Sandbox Code Playgroud)

将上述代码天真地翻译成 clojure 给了我

(defn compose-comparators [& comparators]
  (fn [x y]
    (let [result 
          (->> comparators
              (map #(% x y))
              (filter #(not (zero? %)))
              first)]
      (if (nil? result)
        0
        result))))

(def people-comparision-2 
  (compose-comparators fname-compare lname-compare mname-compare))

(people-comparision-2 person1 person2)

;Output
;Comparing fname
;Comparing lname
;Comparing mname
;14
Run Code Online (Sandbox Code Playgroud)

这不是我所期望的。我在某处读到 clojure 出于性能原因或其他原因一次处理序列的 32 个元素。获得类似于 C# 代码的输出/行为的惯用 Clojure 方式是什么?

以下是我的尝试。然而,它并不觉得“clojurey”。

(defn compose-comparators-2 [& comparators]
  (fn [x y] 
    (loop [comparators comparators
          result 0]
      (if (not (zero? result))
        result
        (let [comparator (first comparators)]
          (if (nil? comparator)
          0
          (recur (rest comparators) (comparator x y))))))))

(def people-comparision-3 
  (compose-comparators-2 fname-compare lname-compare mname-compare))

(people-comparision-3 person1 person2)

;Output
;Comparing fname
;14
Run Code Online (Sandbox Code Playgroud)

编辑

根据这个问题的答案以及相关问题答案,我认为如果我需要提前退出,我应该明确说明。一种方法是将集合转换为惰性集合。另一种选择是用于reduced提前退出reduce循环。

以我目前所掌握的知识,我更倾向于选择显式惰性收集路线。使用以下功能是否存在问题 -

(defn lazy-coll [coll]
  (lazy-seq 
    (when-let [s (seq coll)]
      (cons (first s) (lazy-coll (rest s))))))
Run Code Online (Sandbox Code Playgroud)

这样我就可以使用mapremove我通常会使用的方式。

Thu*_*ail 1

正如您对自己的怀疑以及其他答案所指出的那样,问题在于分块序列并不像它们可能的那样懒惰。

如果我们看看你的compose-comparators函数(稍微简化)

(defn compose-comparators [& comparators]
  (fn [x y]
    (let [result (->> comparators
                      (map #(% x y))
                      (remove zero?)
                      first)]
      (if (nil? result) 0 result))))
Run Code Online (Sandbox Code Playgroud)

...运行所有三个比较的原因people-comparison-2map处理分块序列,正如您在此处看到的。

map一个简单的解决方案是用删除的块替换 a :

(defn lazy-map [f coll]
  (lazy-seq
    (when-let [s (seq coll)]
      (cons (f (first s)) (lazy-map f (rest s))))))
Run Code Online (Sandbox Code Playgroud)

顺便说一句,您可以抽象比较器函数的构造。如果我们定义

(defn comparer [f]
  (fn [x y]
    (println "Comparing with " f)
    (compare (f x) (f y))))
Run Code Online (Sandbox Code Playgroud)

...我们可以用它来定义

(def people-comparision-2 
 (apply compose-comparators (map comparer [:fname :lname :mname])))
Run Code Online (Sandbox Code Playgroud)