Статьи

Случайные мысли о протоколах Clojure

Великие языки — это те, которые предлагают ортогональность в дизайне. Проще говоря, это означает, что ядро ​​языка предлагает минимальный набор непересекающихся способов составления абстракций. В более ранней статье «Случай ортогональности в дизайне» я обсуждал некоторые функции из таких языков, как Haskell, C ++ и Scala, которые помогают вам создавать абстракции более высокого порядка из более мелких, используя методы, предлагаемые этими языками.

В этом посте я обсуждаю новую функцию в Clojure, которая только что появилась в недавно выпущенном 1.2. Я не буду вдаваться в то, что такое протоколы — есть довольно много хороших статей, которые представляют Clojure Protocolsи связанные формы defrecord и deftype. В этом посте мы расскажем о том, как протоколы поощряют неинтрузивное расширение абстракций, не путая наследование с полиморфизмом. Я также обсуждаю некоторые из моих реализаций о том, какие протоколы не являются, что я чувствовал, что было одинаково важно наряду с пониманием того, что они из себя представляют.

Давайте начнем со знакомого класса Show типа языка Haskell.

> :t show
show :: (Show a) => a -> String

Принимает тип и отображает для него строку. Вы получите show для своего класса, если вы реализовали его как экземпляр класса типа Show. Класс Show type расширяет вашу абстракцию прозрачно через дополнительный набор поведения. Мы можем сделать то же самое, используя протоколы в Clojure.

(defprotocol SHOW 
(show [val]))

Определение протокола просто объявляет контракт без какой-либо конкретной реализации в нем. Под покровами он генерирует интерфейс Java, который вы также можете использовать в своем коде Java. Но протокол не интерфейс .

Добавление поведения неинвазивно ..

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

Расширение java.lang.Integer с помощью ШОУ ..

(extend-type Integer
SHOW
(show [i] (.toString i)))

Мы можем также расширить интерфейс. И получить доступ к добавленному поведению из * любой * его реализации. Вот расширение clojure.lang.IPersistentVector ..

(extend-type clojure.lang.IPersistentVector
SHOW
(show [v] (.toString v)))

(show [12 1 4 15 2 4 67])
> "[12 1 4 15 2 4 67]"

И, конечно, я могу расширить свои собственные абстракции с новым поведением ..

(defrecord Name [last first])

(defn name-desc [name]
(str (:last name) " " (:first name)))

(name-desc (Name. "ghosh" "debasish")) ;; "ghosh debasish"

(extend-type Name
SHOW
(show [n]
(name-desc n)))

(show (Name. "ghosh" "debasish")) ;; "ghosh debasish"

Нет наследования

Протоколы помогают вам связывать абстракции, которые никоим образом не связаны друг с другом. И это делает это неинвазивно. Объект соответствует протоколу, только если он реализует контракт. Как я уже упоминал ранее, нет понятия иерархии или наследования, связанных с этой формой полиморфизма.

Нет раздувания объектов, никаких исправлений обезьян

И здесь нет раздувания объектов. Вы можете вызвать show для любой абстракции, для которой вы реализуете протокол, но show никогда не добавляется в качестве метода для этого объекта. В качестве примера попробуйте следующее после реализации SHOW for Integer.

(filter #(= "show" (.getName %)) (.getMethods Integer))

вернет пустой список. Следовательно, нет никакой возможности «случайно» переопределить чей-то патч обезьяны для какого-то общего класса.

Не совсем тип класса

Протоколы Clojure рассылаются по первому аргументу методов. Это ограничивает его возможности получения полной мощности, которую предлагают классы типов Haskell / Scala. Рассмотрим аналог Show в Haskell, который является классом типа Read.

> :t read  
read :: (Read a) => String -> a

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

> [1,2,3] ++ read "[4,5,6]"
=> [1,2,3,4,5,6]

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

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

С http://debasishg.blogspot.com/2010/08/random- центы-on-clojure-protocols.html