2014-12-24 1 views
1

Предположим, что я хочу обработать последовательность, где обработка выполнена с сохранением состояния, и я хочу сделать это ленивым способом с использованием библиотек последовательности Clojure.Lazy обработка состояния с библиотеками последовательностей

В качестве конкретного примера предположим, что я хочу реализовать distinct, который, естественно, реализован как фильтр состояния, отслеживая видимые элементы. Мой первый удар является не вступать в использовании библиотек последовательностей, но вместо этого lazy-seq:

(defn distinct' [coll] 
    (let [process (fn process [coll seen] 
        (lazy-seq 
        (when-let [[x & r] (seq coll)] 
         (if (contains? seen x) 
         (process r seen) 
         (cons x (process r (conj seen x)))))))] 
    (process coll #{}))) 

Я в порядке с вышеизложенным, но я бы предпочел, чтобы вместо этого использовать методы как map и filter если я могу , Я изо всех сил пытаюсь это сделать. Один из подходов, который работает, чтобы использовать reductions:

(defn distinct'' [coll] 
    (->> (reductions (fn [[_ _ seen] x] 
        (if (contains? seen x) 
         [false nil seen] 
         [true x (conj seen x)])) 
        [false nil #{}] 
        coll) 
     (filter first) 
     (map second))) 

Фундаментально, distinct'' выполняет цели использования абстракций более высокого уровня (map, filter, reductions), оставаясь ленивым. Но он не может быть слишком сложным, особенно с вектором, который у меня есть, проходящим в качестве аккумулятора.

Если я пытаюсь напрямую внедрить состояние в предикат, который будет использоваться с filter, в то время как он ближе к тому, что я представляю, это просто кажется «неправильным», и я смущен даже написать следующее код (filter «ы документы даже сказать, что предикат должен быть свободен от побочных эффектов):

(defn distinct''' [coll] 
    (let [seen (atom #{})] 
    (filter (fn [x] 
       (if (contains? @seen x) 
       false 
       (do (swap! seen conj x) 
        true))) 
      coll))) 

Мой вопрос:

есть ли способ сделать ленивую динамическую обработку, как это с библиотеками последовательностей в чистом (даже для этого примера distinct)? Или, lazy-seq часто самый чистый подход?

+3

Похоже, это вопрос мнения. Я думаю, что ленивый подход более ясен. – Bill

+0

Мои 0.02 $: Я не думаю, что последний ошибочен, я не понимаю, что мутирует * внутреннее * состояние как * побочные эффекты *, и это очень читаемая реализация, но согласилась, что это вопрос мнения вообще –

+1

As в стороне, вам следует избегать использования '[x & xs]' destructuring при создании ленивого seq из другого ленивого seq, потому что он заставляет * два * элемента из входного seq, чтобы выяснить, должно ли xs' быть nil или нет, что может быть плохо, если 'xs' дорого производить. Для кода качества библиотеки вы должны использовать 'first' и' rest'. – amalloy

ответ

1

Мне нравится distinct', хотя я бы включил вспомогательную функцию process, например ((fn process ...) coll #{}). Нет ничего плохого в использовании lazy-seq и рекурсии для решения проблемы, и попытка избежать их, сделав все в map/filter, может привести к гораздо менее читаемым программам.

И если вы не возражаете, потянув в равнинной/полезный, вы можете сделать это немного симпатичнее с lazy-loop:

(defn distinct'''' [coll] 
    (lazy-loop [coll coll, seen #{}] 
    (when-let [[x & r] (seq coll)] 
     (if (contains? seen x) 
     (recur r seen) 
     (cons x (lazy-recur r (conj seen x))))))) 

который macroexpands к чему-то эквивалентное вашему distinct'.

Смежные вопросы