Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type roles and confusing behavior by `coerce`

I have a type Id a and I'm trying to prevent accidentally coercing, e.g., an Id Double to an Id Int.

If I understand type roles correctly, the following should not compile.

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

Unfortunately, it does:

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

What am I missing about type roles?

like image 413
Fried Brice Avatar asked Dec 27 '19 18:12

Fried Brice


People also ask

What are the 2 types of coercion?

The two main categories of coercion — deterrence and compellence — are distinct in their nature and requirements.

What are some examples of coercion?

These actions may include extortion, blackmail, or even torture and sexual assault. For example, a bully may demand lunch money from a student where refusal results in the student getting beaten. In common law systems, the act of violating a law while under coercion is codified as a duress crime.

What are coercive tactics?

Coercive tactics, or coercive psychological systems, are defined on their website as unethical mind control such as brainwashing, thought reform, destructive persuasion and coercive persuasion.


1 Answers

Coercible has three possible "types" of instances (which are automagically generated by the compiler, not defined by the user). Only one of them is actually affected by roles.

  • Every type is coercible to itself.
  • You can coerce "under" a type constructor, provided the affected type variables are representational or phantom. For example, you can coerce a Map Char Int into a Map Char (Data.Monoid.Sum Int) because for Map we have type role Map nominal representational.
  • You can always coerce a newtype to the underlying type and vice versa, provided the newtype constructor is in scope. This ignores all roles! The rationale is that, given that the constructor is available, you could always wrap and unwrap manually, so the role doesn't give you any safety anyway.

In your example, the third rule applies. Had the newtype been defined in another module and the constructor not imported, the coercion would have failed (to make it work again, you would need to switch the role to phantom).

The somewhat surprising special behaviour for newtypes is explained in this GHC issue.

like image 61
danidiaz Avatar answered Oct 19 '22 19:10

danidiaz