Я не думаю, что понимаю классы типов. Я где-то читал, что думать о классах типов как о «интерфейсах» (из ОО), которые реализует тип, неправильно и вводит в заблуждение. Проблема в том, что я испытываю проблемы, видя их как нечто иное, и как это неправильно.
Например, если у меня есть класс типов (в синтаксисе Haskell)
class Functor f where
fmap :: (a -> b) -> f a -> f b
Чем это отличается от интерфейса [1] (в синтаксисе Java)
interface Functor<A> {
<B> Functor<B> fmap(Function<B, A> fn)
}
interface Function<Return, Argument> {
Return apply(Argument arg);
}
Одно возможное различие, о котором я могу подумать, состоит в том, что реализация класса типов, используемая при определенном вызове, не указывается, а скорее определяется из среды - скажем, исследуя доступные модули для реализации для этого типа. Похоже, это артефакт реализации, который может быть решен на языке ОО; подобно тому, как компилятор (или среда выполнения) может сканировать оболочку / extender / monkey-patcher, который предоставляет необходимый интерфейс для типа.
Чего мне не хватает?
[1] Обратите внимание, что f a
аргумент был удален, fmap
поскольку, поскольку это язык ОО, вы вызываете этот метод для объекта. Этот интерфейс предполагает, что f a
аргумент был исправлен.
C
без присутствия downcast?В дополнение к превосходному ответу Андреаса, имейте в виду, что классы типов предназначены для упрощения перегрузки , которая влияет на глобальное пространство имен. В Haskell нет перегрузки, кроме той, которую вы можете получить с помощью классов типов. Напротив, когда вы используете объектные интерфейсы, только те функции, которые объявлены для получения аргументов этого интерфейса, должны будут беспокоиться об именах функций в этом интерфейсе. Итак, интерфейсы предоставляют локальные пространства имен.
Например, у вас был
fmap
объектный интерфейс под названием «Functor». Было бы прекрасно иметьfmap
другой интерфейс, скажем «Structor». Каждый объект (или класс) может выбирать, какой интерфейс он хочет реализовать. Напротив, в Haskell у вас может быть только одинfmap
в определенном контексте. Вы не можете импортировать классы типов Functor и Structor в один и тот же контекст.Интерфейсы объектов больше похожи на стандартные подписи ML, чем на классы типов.
источник
В вашем конкретном примере (с классом типа Functor) реализации Haskell и Java ведут себя по-разному. Представьте, что у вас есть тип данных Maybe и вы хотите, чтобы он был Functor (это действительно популярный тип данных в Haskell, который вы также можете легко реализовать в Java). В вашем примере с Java вы заставите класс Maybe реализовать ваш интерфейс Functor. Таким образом, вы можете написать следующее (просто псевдокод, потому что у меня только фон c #):
Обратите внимание, что
res
имеет тип Functor, а не Maybe. Таким образом, это делает реализацию Java практически непригодной, потому что вы теряете конкретную информацию о типе, и вам необходимо выполнять приведения. (по крайней мере, мне не удалось написать такую реализацию, где типы все еще присутствовали). С классами типа Haskell вы получите Maybe Int.источник
Maybe<Int>
.