Да, вы правы, вы ищете алгебраические типы данных.На них есть отличное учебное пособие по Learn You a Haskell .
Кстати, концепция абстрактного класса из ООП фактически имеет три различных перевода на Haskell, и ADT - это всего лишь один,Вот краткий обзор методов.
Алгебраические типы данных
Алгебраические типы данных кодируют шаблон абстрактного класса, подклассы которого известны, и где функции проверяют, какой конкретный экземпляр объекта являетсячлен down-casting.
abstract class IntBox { }
class Empty : IntBox { }
class Full : IntBox {
int inside;
Full(int inside) { this.inside = inside; }
}
int Get(IntBox a) {
if (a is Empty) { return 0; }
if (a is Full) { return ((Full)a).inside; }
error("IntBox not of expected type");
}
Переводит в:
data IntBox = Empty | Full Int
get :: IntBox -> Int
get Empty = 0
get (Full x) = x
Запись функций
Этот стиль не допускает down-down, поэтому Get
функция выше не будет выражаться в этом стиле.Так что здесь есть нечто совершенно иное.
abstract class Animal {
abstract string CatchPhrase();
virtual void Speak() { print(CatchPhrase()); }
}
class Cat : Animal {
override string CatchPhrase() { return "Meow"; }
}
class Dog : Animal {
override string CatchPhrase() { return "Woof"; }
override void Speak() { print("Rowwrlrw"); }
}
Его перевод на Хаскелле не отображает типы в типы.Animal
является единственным типом, а Dog
и Cat
заключены в функции конструктора:
data Animal = Animal {
catchPhrase :: String,
speak :: IO ()
}
protoAnimal :: Animal
protoAnimal = Animal {
speak = putStrLn (catchPhrase protoAnimal)
}
cat :: Animal
cat = protoAnimal { catchPhrase = "Meow" }
dog :: Animal
dog = protoAnimal { catchPhrase = "Woof", speak = putStrLn "Rowwrlrw" }
Существует несколько различных вариантов этой базовой концепции.Инвариант заключается в том, что абстрактный тип является типом записи, где методы являются полями записи.
РЕДАКТИРОВАТЬ: в комментариях есть хорошее обсуждение некоторых тонкостей этого подхода, в том числе ошибки вкод выше.
Классы типов
Это мое наименее любимое кодирование ОО-идей.Программистам OO удобно, потому что он использует знакомые слова и сопоставляет типы с типами.Но описанный выше подход к описанию функций обычно облегчает работу, когда все усложняется.
Я еще раз закодирую пример Animal:
class Animal a where
catchPhrase :: a -> String
speak :: a -> IO ()
speak a = putStrLn (catchPhrase a)
data Cat = Cat
instance Animal Cat where
catchPhrase Cat = "Meow"
data Dog = Dog
instance Animal Dog where
catchPhrase Dog = "Woof"
speak Dog = putStrLn "Rowwrlrw"
Это выглядит хорошо, неЭто?Трудность возникает, когда вы понимаете, что, хотя он выглядит как ОО, на самом деле он не работает как ОО.Возможно, вы захотите иметь список животных, но лучшее, что вы можете сделать прямо сейчас, это Animal a => [a]
, список однородных животных, например.список только кошек или только собак.Затем вам нужно сделать этот тип обертки:
{-# LANGUAGE ExistentialQuantification #-}
data AnyAnimal = forall a. Animal a => AnyAnimal a
instance Animal AnyAnimal where
catchPhrase (AnyAnimal a) = catchPhrase a
speak (AnyAnimal a) = speak a
И тогда [AnyAnimal]
- это то, что вы хотите для своего списка животных.Однако оказывается, что AnyAnimal
предоставляет точно ту же информацию о себе, что и запись Animal
во втором примере, мы только что пошли об этом окольным путем.Поэтому я не считаю, что классы типов являются очень хорошей кодировкой ОО.
И на этом на этой неделе завершается издание Слишком много информации!