What is a role?

What is a role ? I’m seeing a role declaration in purescript-react:

type role ReactClass representational

I don’t really keep up with these rigid distinctions in the hierarchy of types, but I’m also not a total noob when it comes to these things. Can someone give a mid level explanation and maybe a motivation for how it’s expected to be used?

6 Likes

Here’s my 5 minute summary.

It’s part of the new Coercible features. This syntax indicates whether or not one can coerce one type to another type safely (i.e. with compiler guarantees that it’s ok to do this). In short, rather than writing map Age largeArrayOfIntValues, which takes O(n), one can now write (coerce largeArrayOfIntValues :: Array Age), which takes O(1).

The syntax follows this pattern:

-- Given
data    TypeName typeParameter1 typeParameter2 .... typeParameterN
-- or
newtype TypeName typeParameter1 typeParameter2 .... typeParameterN

-- then we use the following syntax to say which role annotation
-- is associated with each type parameter above
type role TypeName typeParameterRole1 typeParameterRole1 .... typeParameterRoleN

There are 3 roles used to annotate type parameters:

  • nominal = the type can never be coerced to another type. Examples are the key types (but not the value types) in a Map type that uses a type-class approach for “calculating” a key.
    • HashMap keyType valueType - since this Map type uses the Hashable type class, changing the key type from Int to SmallInt might change the Hashable instance used to calculate the key’s hash. Therefore, the original key may refer to different values depending on how one coerces the key type.
    • Map keyType valueType - since this Map type uses the Ord type class, the same issue above applies here.
  • representational = the type can be coerced to another type if certain conditions apply. Examples are “container” types that store values.
    • List valueType - if a List stores Int values and there is a newtype Age around Int, we can safely coerce the value from List Int to List Age and vice versa.
    • HashMap keyType valueType - if a Map stores Int values and there is a newtype Age around Int, then we can safely coerce the HashMap keyType Int to HashMap key Age and vice versa.
  • phantom - the type can always be coerced to another type. I believe the next example is correct but another core contributor might correct me. Examples include anything that has a phantom type parameter
    • newtype Tagged phantom a = Tagged a - since phantom isn’t a part of the runtime representation of Tagged, we can change it to whatever we want. Tagged "type-level string" Int can be coerced to Tagged "different type-level string" Int, Tagged READ_ONLY Int, etc.

To understand the rules for determining what is safe, see PureScript/purescript#3999 - kl0tli’s PR where he’s working on the docs for the Coercible type class

17 Likes

Just to add to what Jordan said, roles are an advanced feature which you probably won’t need to care about unless you’re a library author, and even then it’s fairly unlikely you’ll run into them. In the majority of cases, the roles which would be inferred by the compiler should be appropriate, so for most data types you don’t need (and shouldn’t have) role annotations at all. Packages like purescript-react which use the FFI heavily are more likely to want to include role declarations, but even then you only need them if you want to use coerce, which is also an advanced feature, and not something we’re expecting most people to need to use directly.

6 Likes