Интерфейс Java и класс типов Haskell: различия и сходства?


112

Пока я изучаю Haskell, я обратил внимание на его типовой класс , который, как предполагается, был великим изобретением, появившимся на Haskell.

Однако на странице Википедии о классе типов :

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

Что мне кажется довольно близким к интерфейсу Java (цитируя страницу Интерфейса Википедии (Java) ):

Интерфейс в языке программирования Java - это абстрактный тип, который используется для определения интерфейса (в общем смысле этого термина), который классы должны реализовать.

Эти два выглядят довольно похоже: класс типа ограничивает поведение типа, а интерфейс ограничивает поведение класса.

Интересно, в чем разница и сходство между классом типов в Haskell и интерфейсом в Java, или, может быть, они принципиально разные?

EDIT: я заметил, что даже haskell.org признает, что они похожи . Если они так похожи (или таковы?), То почему класс типов вызывает такую ​​шумиху?

БОЛЬШЕ РЕДАКТИРОВАНИЯ: Вау, так много отличных ответов! Думаю, мне придется позволить сообществу решить, какой из них лучший. Однако, читая ответы, все они, кажется, просто говорят, что «класс типов может многое сделать, в то время как интерфейс не может или должен справляться с дженериками» . Я не могу не задаться вопросом, могут ли интерфейсы что-нибудь делать, а классы типов - нет? Кроме того, я заметил, что Википедия утверждает, что класс типов был первоначально изобретен в статье 1989 года * «Как сделать специальный полиморфизм менее специальным», в то время как Haskell все еще находится в колыбели, а проект Java был начат в 1991 году и впервые выпущен в 1995 году. Так что, может быть, вместо того, чтобы класс типов был похож на интерфейсы, а наоборот, на интерфейсы повлиял класс типов?Есть ли какие-то документы / документы, подтверждающие или опровергающие это? Спасибо за все ответы, все они очень полезны!

Спасибо за вклад!


3
Нет, на самом деле интерфейсы не могут делать ничего такого, чего не могут классы типов, с основным предупреждением, что интерфейсы обычно появляются на языках, которые имеют встроенные функции, которых нет в Haskell. Если бы классы типов были добавлены в Java, они также смогли бы использовать эти функции.
CA McCann

8
Если у вас несколько вопросов, вам следует задать несколько вопросов, а не пытаться втиснуть их все в один вопрос. В любом случае, отвечу на ваш последний вопрос: основное влияние Java - это Objective-C (а не C ++, как часто ложно сообщается), чье основное влияние, в свою очередь, - это Smalltalk и C. Интерфейсы Java являются адаптацией протоколов Objective-C, которые снова являются формализация идеи протокола в ОО, которая, в свою очередь, основана на идее сетевых протоколов, в частности ARPANet. Все это произошло задолго до цитируемой вами статьи. ...
Jörg W Mittag

1
... Влияние Haskell на Java пришло намного позже, и оно ограничивается Generics, которые, в конце концов, были разработаны одним из разработчиков Haskell, Филом Вадлером.
Jörg W Mittag

5
Это статья в Usenet Патрика Нотона, одного из первых разработчиков Java: на Java сильно повлиял Objective-C, а не C ++ . К сожалению, он настолько стар, что оригинального сообщения даже нет в архивах Google.
Jörg W Mittag

8
Есть еще один вопрос, который был закрыт как точная копия этого, но на него есть гораздо более подробный ответ: stackoverflow.com/questions/8122109/…
Бен

Ответы:


50

Я бы сказал, что интерфейс похож на класс типа, SomeInterface tгде все значения имеют тип t -> whatever(где whateverне содержит t). Это связано с тем, что с типом отношения наследования в Java и подобных языках вызываемый метод зависит от типа объекта, для которого они вызваны, и ничего больше.

Это означает, что очень сложно сделать что-то вроде add :: t -> t -> tинтерфейса, где он полиморфен более чем по одному параметру, потому что у интерфейса нет способа указать, что тип аргумента и тип возвращаемого значения метода того же типа, что и тип объект, для которого он вызывается (то есть тип "self"). С помощью Generics есть своего рода способы подделать это, создав интерфейс с универсальным параметром, который, как ожидается, будет того же типа, что и сам объект, например, как Comparable<T>это происходит, где вы должны использовать, Foo implements Comparable<Foo>чтобы compareTo(T otherobject)тип имел тип t -> t -> Ordering. Но это по-прежнему требует от программиста следовать этому правилу, а также вызывает головную боль, когда люди хотят создать функцию, использующую этот интерфейс, они должны иметь параметры рекурсивного универсального типа.

Кроме того, у вас не будет таких вещей, как empty :: tпотому, что вы здесь не вызываете функцию, поэтому это не метод.


1
Черты Scala (в основном интерфейсы) позволяют this.type, поэтому вы можете возвращать или принимать параметры «самотипа». В Scala есть совершенно отдельная функция, которую они называют «самотипами», кстати, не имеющая к этому никакого отношения. Ничто из этого не является концептуальной разницей, просто разница в реализации.
глина

45

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

Итак, вот некоторые заметные отличия:

  • Методы интерфейса всегда связаны с экземпляром объекта. Другими словами, всегда существует подразумеваемый параметр this, то есть объект, для которого вызывается метод. Все входные данные для функции класса типа являются явными.
  • Реализация интерфейса должна быть определена как часть класса, реализующего интерфейс. И наоборот, «экземпляр» класса типа может быть определен полностью отдельно от связанного с ним типа ... даже в другом модуле.

В общем, я считаю справедливым сказать, что классы типов более мощные и гибкие, чем интерфейсы. Как бы вы определили интерфейс для преобразования строки в какое-то значение или экземпляр реализующего типа? Конечно, это не невозможно, но результат не будет интуитивно понятным или элегантным. Вы когда-нибудь мечтали о возможности реализовать интерфейс для типа в какой-нибудь скомпилированной библиотеке? И то, и другое легко выполнить с помощью классов типов.


1
Как бы вы расширили классы типов? Могут ли классы типов расширять другие классы типов так же, как интерфейсы могут расширять интерфейсы?
CMCDragonkai

10
Возможно, стоит обновить этот ответ в свете реализаций Java 8 по умолчанию в интерфейсах.
Восстановить Монику

1
@CMCDragonkai Да, вы можете, например, сказать «class (Foo a) => Bar a where ...», чтобы указать, что класс типа Bar расширяет класс типа Foo. Как и Java, здесь Haskell имеет множественное наследование.
Восстановить Монику

В этом случае класс типов не такой же, как протоколы в Clojure, но с безопасностью типов?
nawfal

24

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

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

Это означает, что когда вы используете функцию apply fooк некоторым аргументам типа, принадлежащего классу Foobar, она ищет реализацию, fooспецифичную для этого типа, и использует ее. Это очень похоже на ситуацию с перегрузкой операторов в таких языках, как C ++ / C #, за исключением более гибких и обобщенных.

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

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

Вкратце: они служат схожим целям, но способ их работы несколько отличается, а классы типов значительно более выразительны и, в некоторых случаях, проще в использовании из-за работы с фиксированными типами, а не с частями иерархии наследования.


Я боролся с пониманием иерархии типов в Haskell, что вы думаете о системах типов, таких как Omega? Могут ли они имитировать иерархию типов?
CMCDragonkai

@CMCDragonkai: Я недостаточно знаком с Omega, чтобы действительно сказать, извините.
CA McCann

16

Я прочитал ответы выше. Я чувствую, что могу ответить немного более четко:

«Класс типов» Haskell и «интерфейс» Java / C # или «черта» Scala в основном аналогичны. Между ними нет концептуальных различий, но есть различия в реализации:

  • Классы типов Haskell реализованы с помощью «экземпляров», которые отделены от определения типа данных. В C # / Java / Scala интерфейсы / трейты должны быть реализованы в определении класса.
  • Классы типов Haskell позволяют вам возвращать тип this или self. Тоже самое и с особенностями Scala (this.type). Обратите внимание, что «самотипы» в Scala совершенно не связаны. Java / C # требует сложного обходного пути с обобщениями, чтобы приблизиться к этому поведению.
  • Классы типов Haskell позволяют вам определять функции (включая константы) без входного параметра типа this. Интерфейсы Java / C # и черты Scala требуют входного параметра this во всех функциях.
  • Классы типов Haskell позволяют определять реализации функций по умолчанию. То же самое с чертами Scala и интерфейсами Java 8+. C # может приблизить нечто подобное с помощью методов расширения.

2
Просто чтобы добавить некоторую литературу к этим пунктам ответов, я сегодня прочитал On (Haskell) Type Classes and (C #) Interfaces , который также сравнивается с интерфейсами C # вместо Javas, но должен хорошо дать понимание концептуальной стороны, анализируя понятие интерфейс через языковые границы.
daniel.kahlenberg

Я думаю, что некоторые приближения для таких вещей, как реализации по умолчанию, выполняются более эффективно на C # с абстрактными классами, возможно?
Arwin 02

12

В Master of Programming есть интервью о Haskell с Филом Вадлером, изобретателем классов типов, который объясняет сходство между интерфейсами в Java и классами типов в Haskell:

Метод Java, например:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

очень похож на метод Haskell:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

Итак, классы типов связаны с интерфейсами, но реальным соответствием будет статический метод, параметризованный типом, как указано выше.


Это должен быть ответ, потому что, согласно домашней странице Фила Вадлера , он был основным разработчиком Haskell, в то же время он также разработал расширение Generics для Java, которое позже было включено в сам язык.
Вонг Цзя Хау


8

Прочтите « Расширение программного обеспечения и интеграция с классами типов», где приведены примеры того, как классы типов могут решить ряд проблем, которые не могут решить интерфейсы.

Примеры, перечисленные в документе:

  • проблема выражения,
  • проблема интеграции фреймворка,
  • проблема независимой расширяемости,
  • тирания господствующего разложения, рассыпания и запутывания.

3
Ссылка мертва выше. Попробуйте вместо этого .
Джастин Лейтгеб

1
Что ж, теперь он тоже мертв. Попробуйте это
RichardW

7

Я не могу говорить на уровне "ажиотажа", если мне так кажется. Но да, классы типов во многом похожи. Одна разница , что я могу думать о том , что Haskell вы можете обеспечить поведение для некоторых из класса типов в операциях :

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

который показывает, что есть две операции, равные (==)и не равные (/=), для вещей, которые являются экземплярами Eqкласса типов. Но операция неравенства определяется в терминах равенства (так что вам нужно будет указать только одно), и наоборот.

Таким образом, в вероятно-незаконной-Java это будет примерно так:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

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


6

Они похожи (читай: имеют аналогичное использование) и, вероятно, реализованы аналогичным образом: полиморфные функции в Haskell берут под капот «vtable», в котором перечислены функции, связанные с классом типов.

Эта таблица часто может быть выведена во время компиляции. Вероятно, это не так в Java.

Но это таблица функций , а не методов . Методы привязаны к объекту, классы типов Haskell - нет.

Считайте их похожими на дженерики Java.


3

Как говорит Дэниел, реализации интерфейса определяются отдельно от объявлений данных. И, как указывали другие, существует простой способ определить операции, которые используют один и тот же свободный тип более чем в одном месте. Так что его легко определить Numкак класс типов. Таким образом, в Haskell мы получаем синтаксические преимущества перегрузки операторов, фактически не имея никаких магических перегруженных операторов - только стандартные классы типов.

Еще одно отличие состоит в том, что вы можете использовать методы, основанные на типе, даже если у вас еще нет конкретного значения этого типа!

Например, read :: Read a => String -> a. Так что, если у вас есть достаточно информации о другом типе о том, как вы будете использовать результат «чтения», вы можете позволить компилятору выяснить, какой словарь использовать для вас.

Вы также можете делать такие вещи, как instance (Read a) => Read [a] where...определение экземпляра чтения для любого списка читаемых вещей. Я не думаю, что это возможно в Java.

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


1
Классы нормального типа предназначены для интерфейсов, как классы с несколькими параметрами - для множественной диспетчеризации в ООП; вы получаете соответствующее увеличение мощности как языка программирования, так и головной боли программиста.
CA McCann,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.