В чем разница между зависимостью класса типа в haskell и подтипом в ООП?

Мы часто используем зависимость типа от класса для эмуляции отношения подтипа.

e.g:

когда мы хотим выразить отношение подтипов между Animal, Reptile и Aves в ООП:

abstract class Animal {
    abstract Animal move();
    abstract Animal hunt();
    abstract Animal sleep();
}

abstract class Reptile extends Animal {
    abstract Reptile crawl();
}

abstract class Aves extends Animal {
    abstract Aves fly();
}

мы можем перевести каждый абстрактный класс выше в класс типа в Haskell:

class Animal a where
    move :: a -> a
    hunt :: a -> a
    sleep :: a -> a

class Animal a => Reptile a where
    crawl :: a -> a

class Animal a => Aves a where
    fly :: a -> a

И даже когда нам нужен разнородный список, у нас есть java">Экзистенциальная квантификация .

Итак, мне интересно, почему мы до сих пор говорим, что в Haskell нет подтипов, есть ли еще что-то, что подтипы могут делать, но класс типов не может? Какая между ними связь и разница?


person luochen1990    schedule 27.06.2018    source источник
comment
Я не думаю, что это вопрос того, что подтипы могут делать, чего не могут классы типов, а скорее наоборот. Классы типов Haskell могут выражать отношения, которые я не знаю, как сделать с ООП, хотя это явно зависит от языка. Как бы вы, например, определили Functor в ООП?   -  person Mark Seemann    schedule 27.06.2018


Ответы (1)


Класс типов с одним параметром — это класс типов, который можно представить как набор типов. Если Sub является подклассом (подклассом типов) Super, то набор типов, реализующих Sub, является подмножеством (или равным) набору типов, реализующих Super. Все Monad — это Applicative, а все Applicative — это Functor.

Все, что вы можете сделать с подклассами, вы можете сделать с экзистенциально квантифицированными типами, ограниченными классом типов в Haskell. Это потому, что по сути это одно и то же: в типичном ООП-языке каждый объект с виртуальными методами включает в себя указатель vtable, который совпадает с указателем «словарь», хранящимся в экзистенциально квантифицированном объекте. значение с ограничением класса типов. Vtables экзистенциальны! Когда кто-то дает вам ссылку на суперкласс, вы не знаете, является ли он экземпляром суперкласса или подкласса, вы знаете только, что он имеет определенный интерфейс (либо из класса, либо из «интерфейса» ООП).

На самом деле вы можете сделать больше с помощью обобщенных экзистенциалов Haskell. Например, мне нравится упаковывать действие, возвращающее значение некоторого типа a, вместе с переменной, в которую результат будет записан после завершения действия; источник возвращает значение того же типа, что и переменная, но это скрыто снаружи:

data Request = forall a. Request (IO a) (MVar a)

Поскольку Request скрывает тип a, вы можете хранить несколько запросов разных типов в одном контейнере. Поскольку a полностью непрозрачен, единственное действие, которое вызывающая сторона может сделать с Request, — это запустить действие (синхронно или асинхронно) и записать результат в MVar. Его трудно использовать неправильно!

Разница в том, что в языках ООП вы обычно можете:

  1. Неявное восходящее преобразование — используйте ссылку на подкласс там, где ожидается ссылка на суперкласс, что должно быть сделано явно в Haskell (например, путем упаковки в экзистенциал).

  2. Попытка приведения вниз, что не разрешено в Haskell, если вы не добавите дополнительное ограничение Typeable, в котором хранится информация о типе времени выполнения.

Однако классы типов могут моделировать больше вещей, чем интерфейсы ООП и подклассы, по нескольким причинам. Во-первых, поскольку это ограничения для типов, а не для объектов, вы можете иметь константы, связанные с типом, например mempty в классе типов Monoid:

class Semigroup m where
  (<>) :: m -> m -> m

class (Semigroup m) => Monoid m where
  mempty :: m

В языках ООП обычно нет понятия «статический интерфейс», который позволил бы вам это выразить. Будущая функция «понятий» в C++ является ближайшим эквивалентом.

Другое дело, что подтипы и интерфейсы основаны на одном типе, тогда как у вас может быть класс типов с несколькими параметрами, которые обозначают набор кортежей типов. Вы можете думать об этом как об отношении. Например, набор пар типов, где один может быть приведен к другому:

class Coercible a b where
  coerce :: a -> b

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

class Ref ref m | ref -> m where
  new :: a -> m (ref a)
  get :: ref a -> m a
  put :: ref a -> a -> m ()

instance Ref IORef IO where
  new = newIORef
  get = readIORef
  put = writeIORef

Здесь компилятор знает, что отношение является однозначным или функцией: каждое значение «входа» (ref) отображается ровно на одно значение «выхода». (m). Другими словами, если параметр ref ограничения Ref определяется как IORef, то параметр m должен быть IO — у вас не может быть этой функциональной зависимости, а также отдельного экземпляра, отображающего IORef на другую монаду. , например instance Ref IORef DifferentIO. Этот тип функциональной связи между типами также может быть выражен с помощью связанных типов или более современных семейств типов (которые, на мой взгляд, обычно более понятны).

Конечно, не идиоматично переводить иерархию подклассов ООП непосредственно в Haskell, используя «антишаблон экзистенциального класса типов», который обычно является излишним. Часто существует гораздо более простой перевод, такой как ADT/GADT/записи/функции — примерно это соответствует совету ООП «предпочитать композицию наследованию».

В большинстве случаев, когда вы пишете класс в ООП, в Haskell вам обычно следует искать не класс типов, а скорее модуль. Модуль, который экспортирует тип и некоторые функции, работающие с ним, — это, по сути, то же самое, что и публичный интерфейс класса, когда речь идет об инкапсуляции и организации кода. Для динамического поведения, как правило, лучшим решением является не диспетчеризация на основе типов; вместо этого просто используйте функцию более высокого порядка. В конце концов, это функциональное программирование. :)

person Jon Purdy    schedule 27.06.2018