Include role information in HTML docs · Issue #4116 · purescript/purescript · GitHub
Skip to content

Include role information in HTML docs #4116

Closed

Description

@JordanMartinez

Summary

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.

data Map k v = ---

type role Map nominal representational

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 declaration
data Either :: Type -> Type -> Type
-- | type declaration
data Either l r
-- | role declaration
type role Either representational representational

.. 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)
  • render the role in the final HTML

Activity

  1. hdgarrood commented on Jun 18, 2021

    @hdgarrood
    Contributor

    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.

  2. hdgarrood commented on Jun 18, 2021

    @hdgarrood
    Contributor

    Here's another possibility, just as a discussion starting point:

    Screenshot from 2021-06-18 14-07-19

    I'm imagining the "?" link would take you to some docs explaining what roles are.

  3. JordanMartinez commented on Jun 18, 2021

    @JordanMartinez
    ContributorAuthor

    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.

  4. hdgarrood commented on Jun 18, 2021

    @hdgarrood
    Contributor

    Perhaps that could be a special page on Pursuit?

    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:

    type role Map
      nominal -- k
      representational -- 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?

  5. garyb commented on Jun 18, 2021

    @garyb
    Member

    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.

  6. hdgarrood commented on Jun 18, 2021

    @hdgarrood
    Contributor

    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.

  7. JordanMartinez commented on Jun 18, 2021

    @JordanMartinez
    ContributorAuthor

    Using the comments to indicate what type is what sounds good to me!

    I also agree that we should do both.

  8. garyb commented on Jun 18, 2021

    @garyb
    Member

    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.

  9. hdgarrood commented on Jun 18, 2021

    @hdgarrood
    Contributor

    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.

  10. hdgarrood commented on Jun 18, 2021

    @hdgarrood
    Contributor

    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.

  11. rhendric commented on Jun 18, 2021

    @rhendric
    Member

    For your consideration:

    image

    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.

  12. hdgarrood commented on Jun 18, 2021

    @hdgarrood
    Contributor

    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.

  13. MonoidMusician commented on Jun 19, 2021

    @MonoidMusician
    Contributor

    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.

  14. 6 remaining items

  15. hdgarrood commented on Jun 28, 2021

    @hdgarrood
    Contributor

    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.

  16. hdgarrood commented on Jun 28, 2021

    @hdgarrood
    Contributor

    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.

  17. hdgarrood commented on Jun 28, 2021

    @hdgarrood
    Contributor

    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.

  18. hdgarrood commented on Jun 28, 2021

    @hdgarrood
    Contributor

    Here it is: #4127.

  19. JordanMartinez commented on Jun 29, 2021

    @JordanMartinez
    ContributorAuthor

    So, this could be implemented in a few PRs then?

    1. get non-FFI types' roles to display in HTML docs (i.e. Display roles in HTML docs #4121)
    2. 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)
    3. 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.

  20. hdgarrood commented on Jun 29, 2021

    @hdgarrood
    Contributor

    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.

  21. JordanMartinez commented on Jun 30, 2021

    @JordanMartinez
    ContributorAuthor

    Ok. 1 and 3 it is. I'll continue working on 1.

  22. JordanMartinez commented on Jul 5, 2021

    @JordanMartinez
    ContributorAuthor

    Since 1 is basically done and just pending a review and merge, I'm looking at 3 right now.

    Given

    foreign import data Foo :: Type -> Type -> Type
    type role Foo nominal nominal
    
    data Bar :: Type -> Type -> Type
    data Bar a b
    
    foreign import data Baz :: Type -> Type -> Type
    type role Baz representational representational

    we would ideally render it as

    
    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?

  23. rhendric commented on Jul 5, 2021

    @rhendric
    Member

    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.

  24. JordanMartinez commented on Jul 6, 2021

    @JordanMartinez
    ContributorAuthor

    It looks like Bar's roles are inferred as "representational".

  25. kl0tl commented on Jul 6, 2021

    @kl0tl
    Member

    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 :: forall a b c d. Bar a b -> Bar c d 
    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 :: forall a b c d. Bar a b -> Bar c d
    Error 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
    …
  26. JordanMartinez commented on Jul 6, 2021

    @JordanMartinez
    ContributorAuthor

    @kl0tl If that's the case, any idea why TypeOnly_*'s roles aren't being displayed in the current state of 4121 (comment)?

  27. JordanMartinez commented on Jul 6, 2021

    @JordanMartinez
    ContributorAuthor

    Oh wait, never mind. I'm only inferring roles for FFI declarations. That's why it's not showing up there.

  28. JordanMartinez commented on Jul 6, 2021

    @JordanMartinez
    ContributorAuthor

    3 has been implemented in #4121.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions