Подтвердить что ты не робот

Каковы причины того, что протоколы и мультиметоды в Clojure менее эффективны для полиморфизма, чем классные классы в Haskell?

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

Теперь у Haskell есть несколько действительно удивительных решений проблемы с класса типов. В частности - мы можем сделать:

class Eq a where
  (==) :: a -> a -> Bool
  (/=) :: a -> a -> Bool


member :: (Eq a) => a -> [a] -> Bool
member y [] = False
member y (x:xs) = (x == y) || member y xs

Теперь в Clojure есть multimethods - так вы можете сделать:

(defmulti area :Shape)
(defn rect [wd ht] {:Shape :Rect :wd wd :ht ht})
(defn circle [radius] {:Shape :Circle :radius radius})
(defmethod area :Rect [r]
    (* (:wd r) (:ht r)))
(defmethod area :Circle [c]
    (* (. Math PI) (* (:radius c) (:radius c))))
(defmethod area :default [x] :oops)
(def r (rect 4 13))
(def c (circle 12))
(area r)
-> 52
(area c)
-> 452.3893421169302
(area {})
-> :oops

Также в Clojure у вас protocols - с помощью которого вы можете:

(defprotocol P
  (foo [x])
  (bar-me [x] [x y]))

(deftype Foo [a b c]
  P
  (foo [x] a)
  (bar-me [x] b)
  (bar-me [x y] (+ c y)))

(bar-me (Foo. 1 2 3) 42)
=> 45

(foo
 (let [x 42]
   (reify P
     (foo [this] 17)
     (bar-me [this] x)
     (bar-me [this y] x))))

=> 17

Теперь этот человек делает заявку:

Но существуют протоколы и мульти-методы. Они очень мощные, но не столь мощные, как классные классы Haskell. Вы можете ввести что-то вроде typeclass, указав свой контракт в протоколе. Это только отправляет по первому аргументу, тогда как Haskell может отправлять всю подпись, включая возвращаемое значение. Мульти-методы более мощные, чем протоколы, но не такие мощные, как отправка Haskell.

Мой вопрос: Каковы причины того, что протоколы и мультиметоды в Clojure менее эффективны для полиморфизма, чем типы стеков в Haskell?

4b9b3361

Ответ 1

Ну, очевидно, что протоколы могут отправлять только первый и только первый аргументы. Это означает, что они примерно эквивалентны

 class Foo a where
   bar  :: a -> ...
   quux :: a -> ...
   ...

Где a должен быть первым аргументом. Классы типа Haskell позволяют a появляться в любом месте функции. Таким образом, протоколы являются менее выразительными, чем типы.

Далее следуют мультиметоды. Мультиметоды, если я не ошибаюсь, позволяют отправлять на основе функции всех аргументов. Это выглядит более выразительно, чем Haskell, поскольку вы можете отправлять аргументы одного и того же типа по-разному. Однако это действительно можно сделать в Haskell, как правило, путем переноса аргумента в newtype для отправки.

Несколько вещей, которые нельзя сделать с помощью нескольких методов:

  • Отправка по типу возврата
  • Сохранять значения, полиморфные для всех типов класса классов forall a. Foo a => a

Чтобы увидеть, как 1. вступает в игру, рассмотрим Monoid, он имеет значение mempty :: Monoid m => m. Это не функция, и симулировать это невозможно с помощью Clojure, так как у нас нет какой-либо информации о типе, который мы ожидаем выбрать.

Для 2. рассмотрим read :: Read a => String -> a. В Haskell мы могли бы создать список с типом [forall a. Read a => a], мы существенно отложили вычисление, и теперь мы можем запускать и повторно запускать элементы списка, чтобы попытаться прочитать их как разные значения.

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

Ответ 2

Самое принципиальное отличие состоит в том, что с классами классов отправка производится по типам, а не по значениям. Для его выполнения не требуется никакого значения. Это позволяет гораздо более общие случаи. Наиболее очевидным примером является отправка (часть) типа результата функции. Рассмотрим, например, Класс Haskell Read:

class Read a where
  readsPrec :: Int -> String -> [(a, String)]
  ...

Такая отправка явно невозможна с помощью нескольких методов, которые должны отправлять свои аргументы.

См. также мое более обширное сравнение с обычным OO.