Eu tenho um tipo Id a
e estou tentando evitar a coação acidental, por exemplo, um Id Double
para um Id Int
.
Se eu entendo as funções de digitação corretamente, o seguinte não deve ser compilado.
{-# 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)
Infelizmente, ele faz:
Prelude> :load Id.hs
[1 of 1] Compiling Main ( Id.hs, interpreted )
Ok, one module loaded.
*Main> :type badKey
badKey :: Id Int
O que estou perdendo nas funções de tipo?
a
inId
é uma variável fantasma e não tem impacto no valor real interno. Se você tivessenewtype Id a = Id a
, a coerção teria falhado.type role
era fazer com que não fosse esse o caso. Esta pergunta está perguntando por que isso não funcionou.Respostas:
Coercible
possui três "tipos" possíveis de instâncias (que são gerados automaticamente pelo compilador, não definidos pelo usuário). Apenas um deles é realmente afetado pelas funções .representational
ouphantom
. Por exemplo, você pode coagir umMap Char Int
em umMap Char (Data.Monoid.Sum Int)
porque, comoMap
nós temostype role Map nominal representational
.No seu exemplo, a terceira regra se aplica. Se o newtype tivesse sido definido em outro módulo e o construtor não importado, a coerção teria falhado (para fazê-lo funcionar novamente, você precisaria mudar a função
phantom
).O comportamento especial um tanto surpreendente para os novos tipos é explicado nesta edição do GHC.
fonte