Saisissez les rôles et les comportements déroutants par `coercition`

11

J'ai un type Id aet j'essaie d'empêcher toute contrainte accidentelle, par exemple, un Id Doubleà un Id Int.

Si je comprends bien les rôles de type, les éléments suivants ne doivent pas être compilés.

{-# 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)

Malheureusement, cela:

Prelude> :load Id.hs
[1 of 1] Compiling Main             ( Id.hs, interpreted )
Ok, one module loaded.
*Main> :type badKey
badKey :: Id Int

Qu'est-ce que je manque sur les rôles de type?

Fried Brice
la source
ain Idest une variable fantôme et n'a aucun impact sur la valeur réelle à l'intérieur. Si vous l'aviez fait newtype Id a = Id a, la contrainte aurait échoué.
lehins
@lehins Le but de la type roleétait de faire en sorte que ce ne soit pas le cas. Cette question demande pourquoi cela n'a pas fonctionné.
Joseph Sible-Reinstate Monica

Réponses:

12

Coerciblea trois "types" d'instances possibles (qui sont générés automatiquement par le compilateur, non définis par l'utilisateur). Un seul d'entre eux est réellement affecté par les rôles .

  • Chaque type est contraint à lui-même.
  • Vous pouvez contraindre "sous" un constructeur de type, à condition que les variables de type affectées soient representationalou phantom. Par exemple, vous pouvez contraindre un Map Char Inten un Map Char (Data.Monoid.Sum Int)car pour Mapnous type role Map nominal representational.
  • Vous pouvez toujours contraindre un nouveau type au type sous-jacent et vice versa, à condition que le constructeur de nouveau type soit dans la portée. Cela ignore tous les rôles! La raison en est que, étant donné que le constructeur est disponible, vous pouvez toujours envelopper et dérouler manuellement, de sorte que le rôle ne vous donne aucune sécurité de toute façon.

Dans votre exemple, la troisième règle s'applique. Si le nouveau type avait été défini dans un autre module et que le constructeur n'avait pas été importé, la contrainte aurait échoué (pour le faire fonctionner à nouveau, vous devriez basculer le rôle sur phantom).

Le comportement spécial quelque peu surprenant des nouveaux types est expliqué dans ce numéro de GHC.

danidiaz
la source