У меня есть тип, Id a
и я пытаюсь предотвратить случайное принуждение, например, Id Double
к Id Int
.
Если я правильно понимаю роли типов, следующее не должно компилироваться.
{-# LANGUAGE RoleAnnotations #-}
import Data.Coerce (coerce)
type role Id nominal
newtype Id a = Id String
badKey :: Id Int
badKey = coerce (Id "I point to a Double" :: Id Double)
К сожалению, это делает:
Prelude> :load Id.hs
[1 of 1] Compiling Main ( Id.hs, interpreted )
Ok, one module loaded.
*Main> :type badKey
badKey :: Id Int
Что мне не хватает в типовых ролях?
a
inId
является фантомной переменной и не влияет на действительное значение внутри. Если бы вы имелиnewtype Id a = Id a
, то принуждение было бы неудачным.type role
чтобы это было не так. Этот вопрос спрашивает, почему это не сработало.Ответы:
Coercible
имеет три возможных «типа» экземпляров (которые автоматически генерируются компилятором, а не определяются пользователем). Только один из них на самом деле зависит от ролей .representational
илиphantom
. Например, вы можете привестиMap Char Int
вMap Char (Data.Monoid.Sum Int)
потому что уMap
нас естьtype role Map nominal representational
.В вашем примере применяется третье правило. Если бы новый тип был определен в другом модуле, а конструктор не был импортирован, приведение не удалось бы (чтобы заставить его работать снова, вам нужно было бы переключить роль на
phantom
).Несколько удивительное специальное поведение для новых типов объясняется в этом выпуске GHC.
источник