Добавление пользовательского поведения в последовательности Clojure - PullRequest
6 голосов
/ 16 сентября 2011

В Clojure настолько мощно то, что все основные типы данных реализуют одну и ту же абстракцию последовательности: clojure.lang.ISeq.

Это означает, что такие функции, как «first», «concat», «cons», «map», «rest» и т. Д., Работают в общем для всех этих типов данных.

У меня такой вопрос: как я могу добавить свою собственную пользовательскую функцию в микс и заставить ее работать для всех типов, которые выходят из ISeq?

Одна из первых попыток состояла в том, чтобы определить мой собственный протокол, затем "(расширенный тип clojure.lang.ISeq ...", но это не сработало (компилируется, но не добавляет поведение к фактическим типам) Другая идея состояла в том, чтобы написать макрос, который явно «продлевает тип» явно для всех типов Clojure (PersistentHashMap, PersistentList и т. Д.), Но это кажется хитрым.

Есть ли какой-нибудь элегантный / идиоматический способ сделать это? Может быть, мультиметоды?

Ответы [ 3 ]

8 голосов
/ 16 сентября 2011

Что именно вы пытаетесь сделать?

Если вы пытаетесь добавить поведение к существующим типам: либо напишите обычные функции, которые работают с seqs, либо используйте мультиметоды, либо extend, чтобы делать то, что вы хотите.

Также следует отметить, что большинство «последовательных» типов Clojure (векторы, наборы, карты) не являются самими последовательностями (они не реализуют clojure.lang.ISeq), поэтому вам нужно сделать больше, чем просто добавитьна clojure.lang.ISeq, если вы хотите поддержать их.

3 голосов
/ 16 сентября 2011

Лучший способ сделать это - написать новые функции, используя существующие универсальные функции Clojure , которые правильно обрабатывают различные типы данных.

Примеры таких обобщенных функций:

  • into добавляет элементы в коллекцию любого типа
  • empty возвращает пустую коллекцию того же типа, что и ее параметр

Затем вы можете написать свою собственную универсальную функцию, которая использует их, например ::

(defn take-every-other [coll]
  (into 
    (empty coll)
    (map first (partition 2 coll))))

 (take-every-other [1 2 3 4 5 6])
 => [1 3 5]

 (take-every-other {:a 1 :b 2 :c 3 :d 4})
 => {:a 1, :c 3}

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

3 голосов
/ 16 сентября 2011

Есть статья IBM Developerworks Стюарта Сьерры под названием Решение проблемы выражения с помощью Clojure 1.2 , которая может дать понимание и ответ на ваш вопрос.

Он использует протоколы для определения группы функций для нескольких типов данных и использует extend для расширения существующих классов (типов данных), чтобы они могли использовать эти функции. Это может быть не совсем то, что вы хотите, но это может быть одним из способов решения вашей проблемы.

Здесь также показано, как можно определить пользовательские типы данных (используя defrecord и deftype), которые реализуют существующие протоколы / интерфейсы.

...