Tengo un tipo Id a
y estoy tratando de evitar la coerción accidental, por ejemplo, un Id Double
a un Id Int
.
Si entiendo los tipos de letra correctamente, no se debe compilar lo siguiente.
{-# 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)
Lamentablemente, sí:
Prelude> :load Id.hs
[1 of 1] Compiling Main ( Id.hs, interpreted )
Ok, one module loaded.
*Main> :type badKey
badKey :: Id Int
¿Qué me estoy perdiendo sobre los roles de tipo?
a
inId
es una variable fantasma y no tiene impacto en el valor real en su interior. Si lo hubiera hechonewtype Id a = Id a
, entonces la coerción habría fallado.type role
era hacer que ese no fuera el caso. Esta pregunta es por qué eso no funcionó.Respuestas:
Coercible
tiene tres posibles "tipos" de instancias (que son generadas automáticamente por el compilador, no definidas por el usuario). Solo uno de ellos está afectado por los roles .representational
ophantom
. Por ejemplo, puede coaccionar unMap Char Int
aMap Char (Data.Monoid.Sum Int)
porque porqueMap
tenemostype role Map nominal representational
.En su ejemplo, se aplica la tercera regla. Si el nuevo tipo se hubiera definido en otro módulo y el constructor no se hubiera importado, la coerción habría fallado (para que funcione nuevamente, necesitaría cambiar el rol a
phantom
).El comportamiento especial algo sorprendente para los nuevos tipos se explica en este número de GHC.
fuente