Фил Вадлер назвал эту проблему «проблемой выражения», по его словам:
Цель состоит в том, чтобы определить тип данных по случаям, где можно добавить новые случаи в тип данныхи новые функции над типом данных, без перекомпиляции существующего кода и при сохранении безопасности статического типа.
Одним из решений для расширения типа данных является использование классов типов.
В качестве примера давайте предположим, что у нас есть простой язык для арифметики:
data Expr = Add Expr Expr | Mult Expr Expr | Const Int
run (Const x) = x
run (Add exp1 exp2) = run exp1 + run exp2
run (Mult exp1 exp2) = run exp1 * run exp2
например,
ghci> run (Add (Mult (Const 1) (Const 3)) (Const 2))
5
Если мы хотим реализовать его расширяемым способом, мыследует переключиться на классы типов:
class Expr a where
run :: a -> Int
data Const = Const Int
instance Expr Const where
run (Const x) = x
data Add a b = Add a b
instance (Expr a,Expr b) => Expr (Add a b) where
run (Add expr1 expr2) = run expr1 + run expr2
data Mult a b = Mult a b
instance (Expr a, Expr b) => Expr (Mult a b) where
run (Mult expr1 expr2) = run expr1 * run expr2
Теперь давайте расширим язык, добавив вычитания:
data Sub a b = Sub a b
instance (Expr a, Expr b) => Expr (Sub a b) where
run (Sub expr1 expr2) = run expr1 - run expr2
например,
ghci> run (Add (Sub (Const 1) (Const 4)) (Const 2))
-1
Для получения дополнительной информации об этом подходе ив общем, по проблеме выражения, посмотрите видео Ральфа Леммеля 1 и 2 на канале 9.
Однако, как отмечено в комментариях, это решение меняет семантику.Например, списки выражений больше не являются допустимыми:
[Add (Const 1) (Const 5), Const 6] -- does not typecheck
Более общее решение, использующее побочные продукты сигнатур типов, представлено в функциональном перле "Типы данных a la carte" .См. Также комментарий Вадлера на бумаге.