Согласно TAPL , §23.2:
Параметрический полиморфизм (...), позволяет вводить один фрагмент кода «в общем», используя переменные вместо фактических типов, а затем создается с определенными типами по мере необходимости.Параметрические определения единообразны: все их экземпляры ведут себя одинаково.(...)
Специальный полиморфизм, напротив, позволяет полиморфному значению демонстрировать различное поведение при «просмотре» у разных типов.Наиболее распространенным примером специального полиморфизма является перегрузка, которая связывает один символ функции со многими реализациями;компилятор (или система времени выполнения, в зависимости от того, является ли разрешение перегрузки статическим или динамическим) выбирает подходящую реализацию для каждого приложения функции, основываясь на типах аргументов.
Итак, если выРассмотрим последовательные этапы истории, неуниверсальная официальная Java (известная как pre- J2SE 5.0 , послед. сентябрь 2004 г.) имела специальный полиморфизм - так что вы могли перегрузить метод - ноне параметрический полиморфизм, поэтому вы не можете написать общий метод .Впоследствии, конечно, вы могли сделать и то и другое.
Для сравнения, с самого начала в 1990 Хаскелл был параметрически полиморфным, то есть вы могли написать:
swap :: (A; B) -> (B; A)
swap (x; y) = (y; x)
где A и B - переменные типа, могут быть созданы для всех типов без предположений.
Но ранее не было конструкции, дающей полиморфизм ad-hoc , который предназначен дляПозволяет вам писать функции, которые применяются к нескольким , но не ко всем типам.Типовые классы были реализованы как способ достижения этой цели.
Они позволяют вам описать класс (что-то похожее на интерфейс Java), давая сигнатуру функций, которые вы хотите реализовать для вашего универсального типа.Затем вы можете зарегистрировать несколько (и, надеюсь, несколько ) экземпляров , соответствующих этому классу.Тем временем вы можете написать универсальный метод, такой как:
between :: (Ord a) a -> a -> a -> Bool
between x y z = x ≤ y ^ y ≤ z
, где Ord
- это класс, который определяет функцию (_ ≤ _)
.При использовании (between "abc" "d" "ghi")
- это , разрешенное статически для выбора правильного экземпляра для строк (а не, например, целых чисел) - именно в тот момент, когда происходит перегрузка метода (Java).
Вы можете сделать что-то подобное в Java с ограниченными символами .Но ключевое отличие между Haskell и Java на этом фронте состоит в том, что только Haskell может автоматически выполнять передачу словаря : на обоих языках, учитывая два экземпляра Ord T
, скажем b0
и b1
, вы можетепостроить функцию f
, которая принимает их в качестве аргументов и создает экземпляр для типа пары (b0, b1)
, используя, скажем, лексикографический порядок.Скажи теперь, что тебе дали (("hello", 2), ((3, "hi"), 5))
.В Java вы должны запомнить экземпляры для string
и int
и передать правильный экземпляр (состоящий из четырех приложений f
!), Чтобы применить between
к этому объекту.Haskell может применить композиционность и выяснить, как построить правильный экземпляр, учитывая только наземные экземпляры и конструктор f
(это, конечно, распространяется на другие конструкторы).
Теперь, что касается вывода типа (и это, вероятно, должно быть отдельным вопросом), для обоих языков это неполно , в том смысле, что вы всегда можете написать неаннотированная программа, для которой компилятор не сможет определить тип.
для Haskell, это потому, что он имеет непредсказуемый (он же первоклассный) полиморфизм, для которого вывод типа неразрешим.Обратите внимание, что в этой точке Java ограничивается полиморфизмом первого порядка (то, на чем расширяется Scala).
для Java, это потому, что она поддерживает контравариантный подтип .
Ноэти языки в основном отличаются по диапазону программных операторов , к которым на практике применяется вывод типа , и по важности , придаваемой правильности результатов вывода типа.
Для Haskell логический вывод применяется ко всем «не очень полиморфным» терминам и прилагает серьезные усилия для получения надежных результатов на основе опубликованных расширений известного алгоритма:
- По своей сути вывод Хаскелла основан на Хиндли-Милнера , который дает вам полные результаты сразу после вывода типа приложения, переменных типа (например,
A
иB
в приведенном выше примере) можно создавать только с неполиморфными типами (я упрощаю, но это по сути полиморфизм в стиле ML, который вы можете найти, например, в Ocaml.). - недавний GHC позаботится о том, чтобы аннотация типа могла потребоваться только для привязки let или λ-абстракции, которая имеет тип не-Damas-Milner .
- Haskell пытался держаться относительно близко к этому непогрешимому ядру даже на самых волосатых его участках (например, GADTs ).В любом случае, предлагаемые расширения почти всегда приводятся в документе с доказательством правильности вывода расширенного типа.
Для Java вывод типав любом случае применяется гораздо более ограниченным образом :
До выхода Java 5 в Java не было никакого вывода типа.Согласно языковой культуре Java, тип каждой переменной, метода и динамически размещаемого объекта должен быть явно объявлен программистом .Когда дженерики (классы и методы, параметризованные по типу) были введены в Java 5, язык сохранил это требование для переменных, методов и распределений .Но введение полиморфных методов (параметризованных по типу) диктовало, что либо (i) программист предоставляет аргументы типа метода на каждом сайте вызова полиморфного метода, либо (ii) язык поддерживает вывод аргументов типа метода.Чтобы избежать создания дополнительной технической нагрузки для программистов, разработчики Java 5 решили выполнить вывод типа для определения аргументов типа для полиморфных вызовов методов .( source , выделено мной)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * GJ 1153 * kludgy добавление подстановочных знаков в качестве запоздалой мысли (Обратите внимание, что я не в курсе возможных исправлений, внесенных в J2SE 6.0, хотя).Большая концептуальная разница в подходе заключается в том, что логический вывод Java local , в том смысле, что предполагаемый тип выражения зависит только от ограничений, сгенерированных из системы типов, и от типов его подвыражений, но нев контексте.
Обратите внимание, что партийная линия относительно неполного, а иногда и неправильного вывода типа относительно спокойна.В соответствии с спецификацией :
Обратите внимание также, что вывод типа никак не влияет на надежность.Если выведенные типы являются бессмысленными, вызов приведет к ошибке типа.Алгоритм вывода типов следует рассматривать как эвристику, предназначенную для эффективной работы на практике.Если не удается получить желаемый результат, вместо него можно использовать явные параметры типа.