Я читал электронную книгу Functional Programming Patterns in Scala & Clojure и нашел образец кода, который привел к этому вопросу.Имеет ли clojure эквивалент выхода C#?
Этот фрагмент кода предназначен для сравнения двух объектов Person. Алгоритм сравнения - сначала сравните свои имена FNames, если они равны, то сравнивайте их LName, если они равны, а затем сравнивают их MNames.
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
Дело в том, что в данном примере он будет делать все три сравнения, даже если первые один возвращается не ноль. В этом случае это не проблема. Однако, если бы я написал идиоматический код 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
Наивный перевод приведенного выше кода в 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
И это не то, что я ожидал. Я где-то читал, что clojure обрабатывает 32 элемента последовательности за раз по производительности или что-то в этом роде. Каков идиоматический способ Clojure для получения результата/поведения, аналогичного C# -коду?
Следующая попытка. Однако он не чувствует себя «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
Edit:
на основе ответов на этот вопрос, а также answer to a related question, я думаю, что если мне нужно ранний выход, я должен быть четко об этом. Один из способов - конвертировать коллекцию в ленивый. Другой вариант - использовать reduced
для выхода из цикла сокращения.
Имея знания, которые у меня есть в настоящее время, я более склонен выбирать явный маршрут ленивого сбора. Есть ли проблема с помощью следующей функции, чтобы сделать это -
(defn lazy-coll [coll]
(lazy-seq
(when-let [s (seq coll)]
(cons (first s) (lazy-coll (rest s))))))
Таким образом, я могу использовать map
, remove
так, как я обычно имел бы.
FYI 'defn' уже имеет неявный' do' блок в теле. – noisesmith
В 'compose-comparators' вы можете заменить' (filter # (not (zero?%)) 'With' (filter (дополнение zero?)) 'Или' (удалить ноль?) ' – Thumbnail