Clojure defprotocol как решение проблемы выражения - PullRequest
9 голосов
/ 13 мая 2011

В книге «Радость Clojure» defprotocol предлагается в качестве решения проблемы выражения - «желание реализовать существующий набор абстрактных методов для существующего конкретного класса безнеобходимо изменить код, который определяет либо. "

Приведенный пример выглядит следующим образом:

(defprotocol Concatenatable
  (cat [this other]))

(extend-type String
  Concatenatable
  (cat [this other]
    (.concat this other)))

(cat "House" " of Leaves")
;=> "House of Leaves"

(extend-type java.util.List
  Concatenatable
  (cat [this other]
    (concat this other)))

(cat [1 2 3] [4 5 6])
;=> (1 2 3 4 5 6)

Предполагается, что это невозможно на языке, подобном Java, но как это сделать?отличается от следующего?

public class Util {
  public static String cat(final String first,
                           final String second) {
    return first.concat(second);
  }

  public static <T> List<T> cat(final List<T> first,
                                final List<T> second) {
    final List<T> list = new List<T>(first);
    list.addAll(second);
    return list;
  }
}

В конце концов, оба используются одинаково:

(cat "House" " of Leaves")
Util.cat("House", " of Leaves");

Функция Clojure cat это не a метод для классов String и List, а скорее независимая функция , перегруженная для приема аргументов String или List.

Хотя мне действительно нравитсяClojure, я не понимаю претензий превосходства для этой конструкции.

Ответы [ 2 ]

21 голосов
/ 13 мая 2011

Хорошо. Вы выпускаете эту cat библиотеку Java для большого количества фанфар, и все скачивают ее. Это так здорово, что я хочу сделать свой собственный тип TVCommercial конкатенируемым, чтобы я мог отправлять его в биты вашей библиотеки, которые работают с конкатенируемыми объектами.

Но я не могу, потому что вы звоните Util.cat(obj1, obj2), у которого нет перегрузки для TVCommercial. Я не могу расширить ваш код для обработки моих типов, потому что я не владею вашим кодом.

Вы можете определить Concatenable как интерфейс для решения этой проблемы:

interface Concatenable {
  Concatenable cat(Concatenable other);
}

Но теперь я не могу написать класс, который является одновременно Concatenable и ... Я не знаю, AnimalHandler, который обрабатывает cat s. Протоколы Clojure решают обе проблемы путем децентрализации функций и реализаций диспетчеризации: они живут повсюду, а не в каком-то одном месте. В Java вы выбираете между:

  • Помещение всех типов отправлений в один переключатель / кейс или перегруженный метод
  • Определение интерфейса, обязывающего метод с определенным именем

Clojure в основном делает последнее из них, но поскольку он использует namespaced names, нет опасности конфликта с другими протоколами, которые считают cat хорошим именем функции.

2 голосов
/ 13 мая 2011

Каждый раз, когда появляется новый тип, к которому вы хотите применить вашу функцию cat, вам необходимо «заново открыть» класс Util и добавить перегрузки методов для новых целевых типов.

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

...