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

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

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

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

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

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

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

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

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

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

Спасибо за все входы!

Ответ 1

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

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

Ответ 6

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

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

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

Ответ 7

В " Умах программирования" есть интервью о Хаскеле с Филом Уодлером, изобретателем классов типов, который объясняет сходство между интерфейсами в 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

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

Ответ 8

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

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

Ответ 9

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

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

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

Посмотрите на них, как на Java generics.

Ответ 10

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

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

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

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

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