Мотивация использования фантомных типов заключается в том, чтобы специализировать возвращаемый тип конструкторов данных. Например, рассмотрим:
data List a = Nil | Cons a (List a)
Возвращаемый тип как Nil
и Cons
является List a
по умолчанию (который обобщается для всех списков типа a
).
Nil :: List a
Cons :: a -> List a -> List a
|____|
|
-- return type is generalized
Также отметим, что Nil
фантом конструктор (то есть его тип возвращаемого не зависит от его аргументов, бессодержательно в этом случае, но тем не менее то же самое).
Поскольку Nil
фантом конструктор мы можем специализироваться Nil
любого типа мы хотим (например Nil :: List Int
или Nil :: List Char
).
Нормальные алгебраические типы данных в Haskell позволяют выбирать тип аргументов конструктора данных. Например, мы выбрали тип аргументов для Cons
выше (a
и List a
).
Однако он не позволяет вам выбрать тип возвращаемого значения конструктора данных. Тип возврата всегда обобщен. Это нормально для большинства случаев. Однако есть исключения. Например:
data Expr a = Number Int
| Boolean Bool
| Increment (Expr Int)
| Not (Expr Bool)
Тип конструкторы данных являются:
Number :: Int -> Expr a
Boolean :: Bool -> Expr a
Increment :: Expr Int -> Expr a
Not :: Expr Bool -> Expr a
Как вы можете видеть, тип возвращаемого значения всех конструкторов данных обобщается. Это проблематично, поскольку мы знаем, что Number
и Increment
должны всегда возвращать Expr Int
, а Boolean
и Not
должны всегда возвращать Expr Bool
.
Обратные типы конструкторов данных неверны, потому что они слишком общие. Например, Number
не может возвращать Expr a
, но все же это делает. Это позволяет вам писать неправильные выражения, которые не проверяет тип проверки. Например:
Increment (Boolean False) -- you shouldn't be able to increment a boolean
Not (Number 0) -- you shouldn't be able to negate a number
Проблема заключается в том, что мы не можем указать тип возвращаемых данных конструкторов данных.
Обратите внимание, что все конструкторы данных из Expr
являются фантомные конструкторы (т.е. их возвращение типа не зависит от их аргументов). Тип данных, конструкторы которого являются фантомными конструкторами, называется фантомным типом.
Помните, что возвращаемый тип конструкторов фантомов, таких как Nil
, может быть специализированным для любого типа, который мы хотим. Таким образом, мы можем создать смарт-конструкторы для Expr
следующим образом:
number :: Int -> Expr Int
boolean :: Bool -> Expr Bool
increment :: Expr Int -> Expr Int
not :: Expr Bool -> Expr Bool
number = Number
boolean = Boolean
increment = Increment
not = Not
Теперь мы можем использовать умные конструкторы вместо нормальных конструкторов и наша задача решена:
increment (boolean False) -- error
not (number 0) -- error
Так фантомные Конструкторы полезны при вы хотите специализировать тип возврата конструктора данных, а типы фантомов - это типы данных, конструкторы которых являются фантомными конструкторами.
Обратите внимание, что конструкторы данных, такие как Left
и Right
также фантомные Конструкторы:
data Either a b = Left a | Right b
Left :: a -> Either a b
Right :: b -> Either a b
Причина в том, что хотя тип возврата этих конструкторах данных действительно зависят от их аргументов, но они все еще обобщенно, потому что они лишь частично зависят от их аргументов.
Простой способ узнать, если конструктор данных фантом конструктор:
ли все переменные типа, входящие в возвращаемый тип конструктора данных также появляться в аргументах конструктора данных? Если да, это не фантомный конструктор.
Надеюсь, что это поможет.
http://code.haskell.org/~dons/talks/dons-google-2015-01-27.pdf недоступен. Другой ресурс упоминается здесь https://www.reddit.com/r/haskell/comments/35042c/mirror_for_don_stewarts_google_talk/cqzozm5/ – palik
@palik: я отредактировал вопрос, чтобы указать на Wayback Machine – Cactus