You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Roles were added in v0.14.0 with the work of the Coercible PRs. However, they do not currently appear in the HTML documentation.
Motivation
HTML docs should contain information on roles as that can affect whether coerce can be used or not. One cannot know whether the below type, Map, can be coerced unless they either read the source code or try to coerce a value via the REPL and see if they get a compiler error.
dataMapkv = ---typeroleMapnominalrepresentational
Proposal
Map above does not export its constructors, so I don't think it's a good reference point for discussing where role annotations should appear in the docs. So, let's use Either instead for reference,
In #4100, kind signatures are displayed above the type's definition. For this situation, I think role annotations should be displayed under the type, following what we do in source code. Thus, the complete docs for a type that includes both a kind signature and a role annotation would look like this...
data Either :: Type -> Type -> Type
data Either l r
type role Either representational representational
... before we display anything else.
Right now, after display the type information (i.e. the content above), we then display the comments, then any constructors, and then any instances. Since the three entities above are each represented as three declarations in the AST, each with its own comments, we need to merge those comments together. In #4100, I "merged" the comments together by displaying kind signature comments first and then the type's comments, separating them with a newline. We could take a similar approach here. For example, given the following source code..
-- | kind declarationdataEither :: Type->Type->Type-- | type declarationdataEitherlr-- | role declarationtyperoleEitherrepresentationalrepresentational
.. we would show the following in the docs
kind declaration
type declaration
role declaration
The implementation would follow the same approach used in #4100:
ensure RoleDeclarations are not filtered out
use the augment pass to add role information and merge its comments with existing comments (if any)
This is slightly difficult, because roles are a slightly more advanced feature that most users shouldn't need to care about. I'd like to see if we can think of an alternative way of displaying role information which indicates that it's not as important as the type definition or kind signature. We should also make sure that inferred roles appear in the docs in the same way as declared roles, because the question of whether or roles are given explicitly or not is irrelevant to consumers of the package.
I'm imagining the "?" link would take you to some docs explaining what roles are.
Perhaps that could be a special page on Pursuit? We could use a modified version of the answers in What is a Role?
This is slightly difficult, because roles are a slightly more advanced feature that most users shouldn't need to care about. I'd like to see if we can think of an alternative way of displaying role information which indicates that it's not as important as the type definition or kind signature. We should also make sure that inferred roles appear in the docs in the same way as declared roles, because the question of whether or roles are given explicitly or not is irrelevant to consumers of the package.
I think not having them at the top because they are a less-used feature makes sense. More important information should be shown first. However, by putting them on the bottom, it can be hard to know which role corresponds with which type parameter, especially if a type has a lot of instances that would force you to scroll up/down to see which role corresponds with which type parameter.
Could we perhaps redisplay the left-hand side of the type declaration alongside the role part? Something like your image's Role section above but it would show:
data Map k v
type role Map nominal representational
That fixes the scrolling issue I raise above.
However, I think a better breakdown would be to show the associations in a clearer manner (though I imagine it's harder to do)
data Map
k = nominal
v = representational
While most types won't have 2 or more type parameters, some (e.g. Halogen) could have many and figuring out what the 4th parameter's role is could be hard even with the first example I proposed above.
To me, this feels like a better fit for the purescript/documentation repo, because that's where language documentation lives, and I think linking to that repl from Pursuit is fine. I think we already have documentation in there for roles too.
I agree with all of your reasoning about repeating the data type definition alongside the roles. I also think it's nice to reuse the language syntax so that you don't have to learn a separate syntax to understand what Pursuit is telling you. How about:
typeroleMapnominal-- krepresentational-- v
so that the code would still parse as a type role declaration, and the parameter names are displayed in comments alongside the role they correspond to?
Maybe the role information could be integrated into the type declaration, as another alternative? When hovering over k / v in the data Map k v heading, something like that.
The thing I don't like about that it has poor discoverability, and is perhaps not very accessible... but it definitely appeals to me that we make the information available more contextually somehow. It could perhaps include the kind of the arguments similarly.
I don't see a problem with doing both, personally. I think if we are going to provide tooltips for type parameters, I'd like to include both their kind and their role in the tooltip.
That's true. If we're adding tooltip annotations then maybe we can just print the role declaration as it is, without the name comments then - similar to the way kind declarations are un-name-annotated.
I think there's probably still an argument for printing the role declaration with the name comments even if we are also doing tooltips, if there's potentially a screenful of instances in between the (non-tooltip) role declaration and the data declaration.
The idea would be that nominal and phantom type parameters get subscript annotations, and representational parameters do not, since that's the role that is most common and probably also the most intuitive for the consumer who is not yet comfortable with roles.
I see and agree with the advantage of having the documentation reuse the PS syntax, but I also share misgivings about the discoverability of tooltips and the fact that the syntax forms don't create strong associations between individual roles and parameters. Maybe subscript annotations could be a compromise between slavish adherence to syntax and hiding the roles behind an interaction or at the bottom of the documentation.
I definitely like the idea of leaving representational parameters un-annotated. I think this is my favourite suggestion so far - it seems fairly clear that the subscript bit isn't part of the language syntax.
I like the subscripts. If we add them with ::after in CSS, they won’t be selectable, which may be desirable so that copying the declaration to the clipboard still results in valid code. But it’s a minor point.
I think bringing the foreign import data and data syntaxes closer together would be good, but a syntax change like this shouldn’t sneak through as a side effect of including role information in HTML docs. If we are going to do this I think it needs its own issue so that we can lay out a case for it, discuss it on its own merits, and give tooling authors a chance to object to it. Being able to name parameters in documentation alone isn’t a good enough reason to change the syntax, in my view. In the meantime we could either ignore foreign import data roles in docs or just reproduce the type role declaration underneath them - it’s certainly not ideal, but sufficient for now, I think.
Or we could just come up with parameter names too like @rhendric suggested originally. If we are going to do that though, I think I might prefer we call them t0, t1, t2… so that it’s clear that the compiler produced them.
I’ll write up an issue about foreign import data. This ties into some other unresolved awkward corners in the language which I think we should consider at the same time.
display FFI types' roles in HTML docs using a "Roles" section underneath those declarations (I imagine this to be the easiest thing to do that otherwise quickly addresses the issue, even if it's not ideal)
change the FFI types' roles to be similar to the data ones using compiler-generated type parameter names
Not sure if we'd do 3 if the syntax itself changed, but it could act as a temporary solution.
I think 1) is a good starting point in any case. I suspect 2) and 3) are about equal in difficulty and I also suspect 2) doesn’t get us any closer to doing 3), so I’d suggest not bothering with 2) and going straight to 3) if that’s what we agree we want to do here.
data Foo :: Type -> Type -> Type
data Foo t0nominal t1nominal
data Bar :: Type -> Type -> Type
data Bar a b
data Baz :: Type -> Type -> Type
data Baz t0 t1
However, due to the "omit kind signatures if all type parameters' kinds are Type" rule and "only show non-representational roles" rule, the above code would be rendered as this instead.
data Foo t0nominal t1nominal
data Bar a b
data Baz t0 t1
In other words, would an always-empty type like Bar be distinguishable from a mysteriously-inhabited data type like Foo which has "interesting" roles and/or Baz which doesn't have "interesting" roles during the temporary time period where we haven't dropped the foreign import syntax in #4127?
But otherwise, I'm fine with the documentation not making a distinction between uninhabited and mysteriously-inhabited types, if no constructors are exported either way. If constructors are exported, they are the distinguishing factor.
Bar has no constructors, its parameters cannot affect its representation in any way (it has none) so we infer phantom, not representational, roles:
> import Safe.Coerce
> data Bar a b
> :t coerce ::forallabcd. Barab->Barcd
forall (t15 ::Type) (t16 ::Type) (t19 ::Type) (t20 ::Type) (a ::t15) (b ::t16) (c ::t19) (d ::t20). Bar @t15 @t16 a b ->Bar @t19 @t20 c d
>
> type role Bar representational representational
> :t coerce ::forallabcd. Barab->BarcdError found:
in module $PSCI
at :1:1 - 1:7 (line 1, column 1 - line 1, column 7)
No type class instance was found for
Prim.Coerce.Coercible b3
d7
…
Activity
hdgarrood commentedon Jun 18, 2021
This is slightly difficult, because roles are a slightly more advanced feature that most users shouldn't need to care about. I'd like to see if we can think of an alternative way of displaying role information which indicates that it's not as important as the type definition or kind signature. We should also make sure that inferred roles appear in the docs in the same way as declared roles, because the question of whether or roles are given explicitly or not is irrelevant to consumers of the package.
hdgarrood commentedon Jun 18, 2021
Here's another possibility, just as a discussion starting point:
I'm imagining the "?" link would take you to some docs explaining what roles are.
JordanMartinez commentedon Jun 18, 2021
Perhaps that could be a special page on Pursuit? We could use a modified version of the answers in What is a Role?
I think not having them at the top because they are a less-used feature makes sense. More important information should be shown first. However, by putting them on the bottom, it can be hard to know which role corresponds with which type parameter, especially if a type has a lot of instances that would force you to scroll up/down to see which role corresponds with which type parameter.
Could we perhaps redisplay the left-hand side of the type declaration alongside the role part? Something like your image's Role section above but it would show:
That fixes the scrolling issue I raise above.
However, I think a better breakdown would be to show the associations in a clearer manner (though I imagine it's harder to do)
While most types won't have 2 or more type parameters, some (e.g.
Halogen) could have many and figuring out what the 4th parameter's role is could be hard even with the first example I proposed above.hdgarrood commentedon Jun 18, 2021
To me, this feels like a better fit for the purescript/documentation repo, because that's where language documentation lives, and I think linking to that repl from Pursuit is fine. I think we already have documentation in there for roles too.
I agree with all of your reasoning about repeating the data type definition alongside the roles. I also think it's nice to reuse the language syntax so that you don't have to learn a separate syntax to understand what Pursuit is telling you. How about:
so that the code would still parse as a
type roledeclaration, and the parameter names are displayed in comments alongside the role they correspond to?garyb commentedon Jun 18, 2021
Maybe the role information could be integrated into the type declaration, as another alternative? When hovering over
k/vin thedata Map k vheading, something like that.The thing I don't like about that it has poor discoverability, and is perhaps not very accessible... but it definitely appeals to me that we make the information available more contextually somehow. It could perhaps include the kind of the arguments similarly.
hdgarrood commentedon Jun 18, 2021
I don't see a problem with doing both, personally. I think if we are going to provide tooltips for type parameters, I'd like to include both their kind and their role in the tooltip.
JordanMartinez commentedon Jun 18, 2021
Using the comments to indicate what type is what sounds good to me!
I also agree that we should do both.
garyb commentedon Jun 18, 2021
That's true. If we're adding tooltip annotations then maybe we can just print the role declaration as it is, without the name comments then - similar to the way kind declarations are un-name-annotated.
hdgarrood commentedon Jun 18, 2021
I think there's probably still an argument for printing the role declaration with the name comments even if we are also doing tooltips, if there's potentially a screenful of instances in between the (non-tooltip) role declaration and the data declaration.
hdgarrood commentedon Jun 18, 2021
Also, I think if we used an
<abbr>(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr) for the tooltips it would still be accessible. We wouldn’t have as much control over styling that way, but I think accessibility is more important.rhendric commentedon Jun 18, 2021
For your consideration:
The idea would be that nominal and phantom type parameters get subscript annotations, and representational parameters do not, since that's the role that is most common and probably also the most intuitive for the consumer who is not yet comfortable with roles.
I see and agree with the advantage of having the documentation reuse the PS syntax, but I also share misgivings about the discoverability of tooltips and the fact that the syntax forms don't create strong associations between individual roles and parameters. Maybe subscript annotations could be a compromise between slavish adherence to syntax and hiding the roles behind an interaction or at the bottom of the documentation.
hdgarrood commentedon Jun 18, 2021
I definitely like the idea of leaving representational parameters un-annotated. I think this is my favourite suggestion so far - it seems fairly clear that the subscript bit isn't part of the language syntax.
MonoidMusician commentedon Jun 19, 2021
I like the subscripts. If we add them with ::after in CSS, they won’t be selectable, which may be desirable so that copying the declaration to the clipboard still results in valid code. But it’s a minor point.
6 remaining items
hdgarrood commentedon Jun 28, 2021
I think bringing the
foreign import dataanddatasyntaxes closer together would be good, but a syntax change like this shouldn’t sneak through as a side effect of including role information in HTML docs. If we are going to do this I think it needs its own issue so that we can lay out a case for it, discuss it on its own merits, and give tooling authors a chance to object to it. Being able to name parameters in documentation alone isn’t a good enough reason to change the syntax, in my view. In the meantime we could either ignoreforeign import dataroles in docs or just reproduce thetype roledeclaration underneath them - it’s certainly not ideal, but sufficient for now, I think.hdgarrood commentedon Jun 28, 2021
Or we could just come up with parameter names too like @rhendric suggested originally. If we are going to do that though, I think I might prefer we call them
t0, t1, t2…so that it’s clear that the compiler produced them.hdgarrood commentedon Jun 28, 2021
I’ll write up an issue about
foreign import data. This ties into some other unresolved awkward corners in the language which I think we should consider at the same time.hdgarrood commentedon Jun 28, 2021
Here it is: #4127.
JordanMartinez commentedon Jun 29, 2021
So, this could be implemented in a few PRs then?
dataones using compiler-generated type parameter namesNot sure if we'd do 3 if the syntax itself changed, but it could act as a temporary solution.
hdgarrood commentedon Jun 29, 2021
I think 1) is a good starting point in any case. I suspect 2) and 3) are about equal in difficulty and I also suspect 2) doesn’t get us any closer to doing 3), so I’d suggest not bothering with 2) and going straight to 3) if that’s what we agree we want to do here.
JordanMartinez commentedon Jun 30, 2021
Ok. 1 and 3 it is. I'll continue working on 1.
JordanMartinez commentedon Jul 5, 2021
Since 1 is basically done and just pending a review and merge, I'm looking at 3 right now.
Given
we would ideally render it as
However, due to the "omit kind signatures if all type parameters' kinds are
Type" rule and "only show non-representationalroles" rule, the above code would be rendered as this instead.In other words, would an always-empty type like
Barbe distinguishable from a mysteriously-inhabited data type likeFoowhich has "interesting" roles and/orBazwhich doesn't have "interesting" roles during the temporary time period where we haven't dropped theforeign importsyntax in #4127?rhendric commentedon Jul 5, 2021
We'd infer phantom roles for
Bar, right?But otherwise, I'm fine with the documentation not making a distinction between uninhabited and mysteriously-inhabited types, if no constructors are exported either way. If constructors are exported, they are the distinguishing factor.
JordanMartinez commentedon Jul 6, 2021
It looks like
Bar's roles are inferred as "representational".kl0tl commentedon Jul 6, 2021
Barhas no constructors, its parameters cannot affect its representation in any way (it has none) so we infer phantom, not representational, roles:JordanMartinez commentedon Jul 6, 2021
@kl0tl If that's the case, any idea why
TypeOnly_*'s roles aren't being displayed in the current state of 4121 (comment)?JordanMartinez commentedon Jul 6, 2021
Oh wait, never mind. I'm only inferring roles for FFI declarations. That's why it's not showing up there.
JordanMartinez commentedon Jul 6, 2021
3 has been implemented in #4121.