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