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


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

однако, в страница Википедии на типе class:

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

который, кажется, скорее близко к интерфейс Java мне (цитирую интерфейс Википедии (Java) страница):

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

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

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

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

ФАЙЛЫ: Вау, так много замечательных ответов! Я думаю, мне придется позволить сообществу решить, какой из них лучший. Однако, читая ответы, все они кажется, просто сказать, что "есть много вещей, которые typeclass может сделать, в то время как интерфейс не может или должен справиться с дженериками". Я не могу не задаться вопросом, есть ли что-нибудь интерфейсы могут делать в то время как typeclasses не может?может быть, вместо того, чтобы typeclass был похож на интерфейсы, его наоборот, что интерфейсы были под влиянием typeclass? есть ли какие-либо документы/документы, подтверждающие или опровергающие это? Спасибо за все ответы, они все очень поучительно!

Спасибо за все материалы!

10 82

10 ответов:

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

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

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

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

с этим из пути, вот некоторые заметные различия:

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

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

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

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

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

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

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

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

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

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

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

смотрите выступление Филиппа Уодлера Вера, эволюция и языки программирования. Уодлер работал над Haskell и был крупным вкладчиком в Java Generics.

на мастерские умы программирования, есть интервью о 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; 
   }

очень похоже на метод Хаскелла:

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

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

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

примеры, перечисленные в статье:

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

Я не могу говорить на уровне "шумихи", если это кажется таким прекрасным. Но да, классы типов похожи во многих отношениях. Одно отличие, о котором я могу думать, заключается в том, что 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); 
    }
}

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

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

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

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

увидеть их скорее как дженерики Java.

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

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

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

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

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