Я действительно считаю, что полиморфизм возвращаемых типов является одной из лучших особенностей классов типов. После того, как я использовал его некоторое время, мне иногда трудно вернуться к моделированию в стиле ООП, где у меня его нет.
Рассмотрим кодирование алгебры. В Haskell у нас есть класс типов Monoid
(игнорируя mconcat
)
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Как мы могли бы закодировать это как интерфейс на языке OO? Короткий ответ: мы не можем. Это потому , что тип mempty
является так (Monoid a) => a
называемым, типом возвращаемого значения полиморфизма. Умение моделировать алгебру невероятно полезно для ИМО. *
Вы начинаете свое сообщение с жалобы на «ссылочную прозрачность». Это поднимает важный вопрос: Haskell является ценностно-ориентированным языком. Таким образом, такие выражения, как read 3
не нужно понимать как вещи, которые вычисляют значения, их также можно понимать как значения. Это означает, что реальная проблема не в полиморфизме возвращаемого типа: это значения с полиморфным типом ( []
и Nothing
). Если язык должен иметь их, то он действительно должен иметь полиморфные возвращаемые типы для согласованности.
Должны ли мы быть в состоянии сказать, []
имеет тип forall a. [a]
? Я так думаю. Эти функции очень полезны, и они делают язык намного проще.
Если бы у Haskell был подтип, полиморфизм []
мог бы быть подтипом для всех [a]
. Проблема в том, что я не знаю способа кодирования, при котором тип пустого списка не будет полиморфным. Рассмотрим, как это будет сделано в Scala (это короче, чем в каноническом статически типизированном языке ООП, Java)
abstract class List[A]
case class Nil[A] extends List[A]
case class Cons[A](h: A. t: List[A]) extends List[A]
Даже здесь, Nil()
это объект типа Nil[A]
**
Другое преимущество полиморфизма возвращаемого типа заключается в том, что он значительно упрощает встраивание по Карри-Говарду.
Рассмотрим следующие логические теоремы:
t1 = forall P. forall Q. P -> P or Q
t2 = forall P. forall Q. P -> Q or P
Мы можем тривиально описать их как теоремы в Haskell:
data Either a b = Left a | Right b
t1 :: a -> Either a b
t1 = Left
t2 :: a -> Either b a
t2 = Right
Подводя итог: мне нравится полиморфизм возвращаемого типа, и я думаю, что он нарушает прозрачность ссылок только в том случае, если у вас ограниченное представление о значениях (хотя это менее убедительно в случае класса специального типа). С другой стороны, я нахожу ваши аргументы в пользу MR и убедительными наборами шрифтов по умолчанию.
*. В комментариях ysdx указывает, что это не совсем верно: мы могли бы повторно реализовать классы типов, моделируя алгебру как другой тип. Как и Java:
abstract class Monoid<M>{
abstract M empty();
abstract M append(M m1, M m2);
}
Затем вы должны передать объекты этого типа с вами. В Scala есть понятие неявных параметров, которое позволяет избежать некоторых, но, по моему опыту, не всех, накладных расходов на явное управление этими вещами. Помещение ваших служебных методов (фабричных методов, двоичных методов и т. Д.) В отдельный F-ограниченный тип оказывается невероятно хорошим способом управления вещами в ОО-языке, который имеет поддержку обобщенных типов. Тем не менее, я не уверен, что я бы испортил этот шаблон, если бы у меня не было опыта моделирования вещей с помощью классов типов, и я не уверен, что другие люди будут.
Он также имеет ограничения, из коробки нет способа получить объект, который реализует класс типов для произвольного типа. Вы должны либо передать значения явно, использовать что-то вроде последствий Scala, либо использовать какую-то форму технологии внедрения зависимостей. Жизнь становится безобразной. С другой стороны, приятно, что у вас может быть несколько реализаций для одного и того же типа. Что-то может быть моноидом несколькими способами. Кроме того, ношение этих структур по отдельности делает ИМО более математически современным, конструктивным, чувствующим его. Таким образом, хотя я все еще предпочитаю делать это на Haskell, я, вероятно, переоценил свой случай.
Классы типов с полиморфизмом возвращаемых типов делают такие вещи простыми в обращении. Это не значит, что это лучший способ сделать это.
**. Йорг Миттаг отмечает, что это не совсем канонический способ сделать это в Scala. Вместо этого мы бы следовали стандартной библиотеке с чем-то вроде:
abstract class List[+A] ...
case class Cons[A](head: A, tail: List[A]) extends List[A] ...
case object Nil extends List[Nothing] ...
Это использует поддержку Scala для нижних типов, а также для параметров ковариантного типа. Так что, Nil
типа Nil
нет Nil[A]
. На данный момент мы довольно далеки от Haskell, но интересно отметить, как Haskell представляет нижний тип
undefined :: forall a. a
То есть это не подтип всех типов, это полиморфно (sp) член всех типов.
Еще больше возвращаемого типа полиморфизма.