Інтерфейс Java та клас типу Haskell: відмінності та схожість?


112

Поки я навчаюсь Haskell, я помітив його клас типу , який повинен бути чудовим винаходом, що походить від Haskell.

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

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

Що мені здається досить близьким до інтерфейсу Java (цитую сторінку інтерфейсу Вікіпедії (Java) ):

Інтерфейс у мові програмування Java - це абстрактний тип, який використовується для визначення інтерфейсу (у загальному сенсі цього терміна), який повинні реалізувати класи.

Ці два виглядають досить схоже: тип класу обмежує поведінку типу, тоді як інтерфейс обмежує поведінку класу.

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

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

БІЛЬШЕ редагування: Вау, так багато чудових відповідей! Я думаю, мені доведеться дозволити громаді вирішити, який найкращий варіант. Однак, читаючи відповіді, всі вони, здається, просто говорять про те, що "існує багато речей, якими може користуватися тип класу, в той час як інтерфейс не може або не справляється із загальними характеристиками" . Я не можу не задатись питанням: чи можна робити що-небудь інтерфейси, коли не можуть бути клас-класи? Також я зауважив, що Вікіпедія стверджує, що початковий клас був винайдений у документі 1989 року * "Як зробити спеціальний поліморфізм менш спеціальним", тоді як Haskell все ще знаходиться в колисці, тоді як проект Java був розпочатий в 1991 році і вперше випущений в 1995 році Тож, може бути, замість того, щоб typeclass був схожий на інтерфейси, навпаки, що на інтерфейси впливав typeclass?Чи є якісь документи / документи, які це підтверджують або спростовують? Дякую за всі відповіді, вони всі дуже освітянські!

Дякую за всі матеріали!


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, Філом Вадлером.
Йорг W Міттаг

5
Це стаття Usenet Патріка Наутона, одного з оригінальних дизайнерів Java: на Java сильно вплинув Objective-C, а не C ++ . На жаль, вона настільки стара, що оригінальна публікація навіть не з’являється в архівах Google.
Йорг W Міттаг

8
Є ще одне питання, яке було закрито як точний дублікат цього, але воно має набагато більш глибоку відповідь: stackoverflow.com/questions/8122109/…
Бен

Відповіді:


50

Я б сказав, що інтерфейс схожий на клас типу, 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ви не викликаєте тут функцію, тож це не метод.


1
Рисунки Scala (в основному інтерфейси) дозволяють this.type, щоб ви могли повернути або прийняти параметри "типу self". У Scala є абсолютно окрема функція, яку вони називають "self type" btw, яка не має нічого спільного з цим. Ніщо з цього не є концептуальною різницею, лише різницею в реалізації.
глина

45

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

Із цим не виходить, ось деякі помітні відмінності:

  • Методи інтерфейсів завжди асоціюються з об'єктом об'єкта. Іншими словами, завжди мається на увазі параметр "цей", який є об'єктом, на який називається метод. Усі входи до функції класу типу явні.
  • Реалізація інтерфейсу повинна бути визначена як частина класу, який реалізує інтерфейс. І навпаки, клас типу "екземпляр" можна визначити повністю відокремленим від його асоційованого типу ... навіть у іншому модулі.

Взагалі, я вважаю, що справедливо сказати, що класи типів є більш потужними та гнучкими, ніж інтерфейси. Як би ви визначили інтерфейс для перетворення рядка в якесь значення або екземпляр типу реалізації? Це, звичайно, не неможливо, але результат не був би інтуїтивним чи елегантним. Ви коли-небудь хотіли, щоб можна було реалізувати інтерфейс для типу в якійсь складеній бібліотеці? Вони обидва легко виконати з класами типів.


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

Це означає, що при використанні функції застосувати функцію fooдо деяких аргументів типу, що належать до класу Foobar, він шукає реалізацію, fooспецифічну для цього типу, і використовує це. Це дуже схоже на ситуацію з перевантаженням оператора такими мовами, як C ++ / C #, за винятком більш гнучких та узагальнених.

Інтерфейси служать подібній меті в мовах ОО, але основна концепція дещо інша; Мови OO поставляються із вбудованим поняттям ієрархій типів, яких у Haskell просто немає, що в деякій мірі ускладнює питання, оскільки інтерфейси можуть спричиняти як перевантаження за допомогою підтипу (тобто, виклику методів у відповідних екземплярах, підтипів, що реалізують інтерфейси для своїх суперпертипів) і шляхом диспетчеризації на основі типу (оскільки два класи, що реалізують інтерфейс, можуть не мати загального надкласу, який також реалізує його). Враховуючи величезну додаткову складність, що вводиться підтипом, я вважаю, що корисніше розглядати класи типів як вдосконалену версію перевантажених функцій на мові, що не є OO.

Варто також зазначити, що класи типів мають набагато більш гнучкі засоби диспетчеризації - інтерфейси, як правило, застосовуються лише до одного класу, що реалізує його, тоді як класи типів визначаються для типу , який може з'являтися в будь-якому місці підпису функцій класу. Еквівалент цього в інтерфейсах OO дозволяв би інтерфейсу визначати способи передачі об'єкта цього класу іншим класам, визначати статичні методи та конструктори, які вибирали б реалізацію на основі того, який тип повернення потрібен у контексті виклику, визначають методи, які приймати аргументи того ж типу, що і клас, що реалізує інтерфейс, і різні інші речі, які насправді зовсім не перекладаються.

Коротше кажучи: вони служать подібним цілям, але спосіб їх роботи дещо відрізняється, і класи типів є значно виразнішими, а в деяких випадках і простішими у використанні через роботу над фіксованими типами, а не частинками ієрархії спадкування.


Я боровся з розумінням ієрархій типів в Haskell, що ви думаєте про тип типів систем, таких як Omega? Чи могли вони імітувати ієрархії типів?
CMCDragonkai

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

16

Я прочитав вищевказані відповіді. Я відчуваю, що можу відповісти трохи чіткіше:

Клас типу "Haskell" та інтерфейс Java / C # "або" черта "Scala" в основному аналогічні. Концептуальної різниці між ними немає, але є відмінності в реалізації:

  • Класи типу Haskell реалізуються з "екземплярами", які відокремлені від визначення типу даних. У C # / Java / Scala інтерфейси / ознаки повинні бути реалізовані у визначенні класу.
  • Класи типу Haskell дозволяють повернути цей тип або тип власного типу. Риси Scala також добре (цей тип). Зауважте, що "типи самоврядування" у Scala є абсолютно незв'язаною особливістю. Для наближення такої поведінки для Java / C # потрібен безладний спосіб вирішення питань із загальними характеристиками.
  • Класи типу Haskell дозволяють визначати функції (включаючи константи) без введення параметра "цей" тип. Інтерфейси Java / C # та риси Scala вимагають вхідного параметра "цей" для всіх функцій.
  • Класи типу Haskell дозволяють визначити реалізацію за замовчуванням для функцій. Так само роблять риси Scala та інтерфейси Java 8+. C # може наблизити щось подібне методами розширень.

2
Для того, щоб додати трохи літератури до цього пункту відповідей, я сьогодні прочитав On (Haskell) Class Class Class та (C #) інтерфейси , який також порівнюється з інтерфейсами C # замість Javas, але повинен добре зрозуміти концептуальну сторону, що розбирає поняття інтерфейс через мовні межі.
daniel.kahlenberg

Я думаю, що деякі наближення до таких речей, як реалізація за замовчуванням, робляться досить ефективно в C # з абстрактними класами, можливо?
Арвін

12

У програмі Master розуму програмування є інтерв'ю про 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

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

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

що показує, що для речей, що є екземплярами, є дві операції, рівні (==)і не рівні(/=)Eq класу типів, . Але нерівна операція визначається через рівні (так що вам потрібно було б надати лише одну), і навпаки.

Тож у, мабуть, не законному Яві, це було б щось на зразок:

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

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

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


6

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

Ця таблиця часто може бути виведена під час компіляції. Це, мабуть, менш вірно на Java.

Але це таблиця функцій , а не методи . Методи прив’язані до об'єкта, типи класів Haskell - ні.

Дивіться на них, як на дженерики Java.


3

Як каже Даніель, реалізація інтерфейсу визначена окремо від декларацій даних. І як зазначали інші, існує простий спосіб визначення операцій, які використовують один і той же вільний тип у більш ніж одному місці. Тож його легко визначити Numяк тип класу. Таким чином, у Haskell ми отримуємо синтаксичні переваги перевантаження оператора, не маючи насправді жодних магічних перевантажених операторів - просто стандартних класів.

Ще одна відмінність полягає в тому, що ви можете використовувати методи, засновані на типі, навіть коли у вас ще немає конкретного значення цього типу!

Наприклад, read :: Read a => String -> a. Отже, якщо у вас є достатня кількість іншої інформації про тип про те, як ви будете використовувати результат "читання", ви можете дозволити компілятору розібратися, який словник використовувати для вас.

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

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


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