Skip to content

Display kind signatures and comments in docs#4100

Merged
JordanMartinez merged 35 commits intopurescript:masterfrom
JordanMartinez:fixDocsOfTypesWithKindSigs
Jun 22, 2021
Merged

Display kind signatures and comments in docs#4100
JordanMartinez merged 35 commits intopurescript:masterfrom
JordanMartinez:fixDocsOfTypesWithKindSigs

Conversation

@JordanMartinez
Copy link
Copy Markdown
Contributor

@JordanMartinez JordanMartinez commented Jun 4, 2021

Description of the change

Fixes #3973


Remaining work

  • Add a single line that separates a kind signature from its type declaration
  • Make inferred kind signatures appear in the docs

Checklist:

  • Added the change to the changelog's "Unreleased" section with a reference to this PR (e.g. "- Made a change (#0000)")
  • Added myself to CONTRIBUTORS.md (if this is my first contribution)
  • Linked any existing issues or proposals that this pull request should close
  • Updated or added relevant documentation
  • Added a test for the contribution (if applicable)

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

Latest changes now make data, newtype, and type display their kind signatures in the docs and merge their comments together. However, class still doesn't display its kind signatures or docs above them.

I got the newtype and type to work after realizing that reorder wasn't including KindDeclaration in its consideration because declName excludes them. I added declDocName as a temporary solution to ensure KindDeclarations are included.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

CI currently fails because the number of args for various Declaration types has increased by one. Thus, the test code is failing.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

I think the number of changes I have to do here show that it would be better if we had a desugar pass that moved KindDeclarations into a DataDeclaration, TypeSynonymDeclaration, and TypeClassDeclaration. Most of this PR is just dealing with this one issue.

If we did that, we might also want to merge RoleDeclarations into their corresponding DataDeclaration.

@JordanMartinez JordanMartinez marked this pull request as ready for review June 9, 2021 13:57
@JordanMartinez
Copy link
Copy Markdown
Contributor Author

CI still fails because there is a breaking change with the docs.json format. There's also a few purs graph tests that are failing, but I'm not sure if that's related to the docs.json issue or something else.

Class declarations still don't show up in the resulting output, but I thought the work I've done was good enough for some initial feedback.

I've now moved the kind information into its own type called KindInfo, which just wraps the keyword and the kind signature, so that we can display the content without having to extract which keyword was used in the info arg of Declaration. It makes the code cleaner and minimizes the changes made to other code.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

So, the final part of this PR that needs to be figured out is why the class kind signatures aren't being included in the rendered docs.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

The tests pass locally. This PR is ready for review.

Copy link
Copy Markdown
Contributor

@hdgarrood hdgarrood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great so far!

>>> filterInstances mn exps
>>> maybe id reorder exps

f :: Maybe [DeclarationRef] -> (Maybe Declaration, [Declaration]) -> Declaration -> (Maybe Declaration, [Declaration])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer that we update isExported to handle kind declarations, rather than duplicating a slightly different version of it inline here. In general, I've found that having two slightly different versions of functions is a really good way to introduce bugs, especially as other changes go in over time. I think the best thing to do is to arrange things so that only one version of the function is necessary, but in cases where that's too difficult, then having one function which can work in both cases is still preferable to duplicating the implementation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like isExported is only used for docs.

How about I convert my inline code into a function called filterDeclarations and remove isExported completely?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait... Now I think I get what you're saying. My original approach is making this needlessly complicated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, longer term I think it would be good to remove this whole module, because it duplicates the existing logic in the desugaring pipeline for figuring out what is and isn't exported from a module. Ideally I think we would be able to reuse that logic for docs too, perhaps by looking at externs to figure out what is and isn't exported.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

Thanks for the direction, Harry! The latest commits show your idea works. Now, I just need to figure out how to deal with the (k :: Type) part.

CI currently fails with these errors
purescript> Failures:
purescript> 
purescript>   tests/TestDocs.hs:37:13: 
purescript>   1) docs.Language.PureScript.Docs, Doc generation tests:, in module KindSignatureDocs, KindSignatureDocs.DImplicit should have the kind signature `data DImplicit :: forall k. k -> Type`
purescript>        expected the kind signature for DImplicit
purescript>        to be `data DImplicit :: forall k. k -> Type`
purescript>          got `data DImplicit :: forall (k :: Type). k -> Type`
purescript> 
purescript>   To rerun use: --match "/docs/Language.PureScript.Docs/Doc generation tests:/in module KindSignatureDocs/KindSignatureDocs.DImplicit should have the kind signature `data DImplicit :: forall k. k -> Type`/"
purescript> 
purescript>   tests/TestDocs.hs:37:13: 
purescript>   2) docs.Language.PureScript.Docs, Doc generation tests:, in module KindSignatureDocs, KindSignatureDocs.TImplicit should have the kind signature `type TImplicit :: forall k. k -> Type`
purescript>        expected the kind signature for TImplicit
purescript>        to be `type TImplicit :: forall k. k -> Type`
purescript>          got `type TImplicit :: forall (k :: Type). k -> Type`
purescript> 
purescript>   To rerun use: --match "/docs/Language.PureScript.Docs/Doc generation tests:/in module KindSignatureDocs/KindSignatureDocs.TImplicit should have the kind signature `type TImplicit :: forall k. k -> Type`/"
purescript> 
purescript>   tests/TestDocs.hs:37:13: 
purescript>   3) docs.Language.PureScript.Docs, Doc generation tests:, in module KindSignatureDocs, KindSignatureDocs.NImplicit should have the kind signature `newtype NImplicit :: forall k. k -> Type`
purescript>        expected the kind signature for NImplicit
purescript>        to be `newtype NImplicit :: forall k. k -> Type`
purescript>          got `newtype NImplicit :: forall (k :: Type). k -> Type`
purescript> 
purescript>   To rerun use: --match "/docs/Language.PureScript.Docs/Doc generation tests:/in module KindSignatureDocs/KindSignatureDocs.NImplicit should have the kind signature `newtype NImplicit :: forall k. k -> Type`/"
purescript> 
purescript>   tests/TestDocs.hs:37:13: 
purescript>   4) docs.Language.PureScript.Docs, Doc generation tests:, in module KindSignatureDocs, KindSignatureDocs.CImplicit should have the kind signature `class CImplicit :: forall k1. (k1 -> Type) -> k1 -> Constraint`
purescript>        expected the kind signature for CImplicit
purescript>        to be `class CImplicit :: forall k1. (k1 -> Type) -> k1 -> Constraint`
purescript>          got `class CImplicit :: forall (k1 :: Type). (k1 -> Type) -> k1 -> Constraint`
purescript> 
purescript>   To rerun use: --match "/docs/Language.PureScript.Docs/Doc generation tests:/in module KindSignatureDocs/KindSignatureDocs.CImplicit should have the kind signature `class CImplicit :: forall k1. (k1 -> Type) -> k1 -> Constraint`/"
purescript> 
Screenshot of `DImplicit`'s generated docs as of latest commits

inferred-kind

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

If we're going to generate inferred kind signatures too, should we omit the signature if the kind is (Type ->)* Type?

Could you help me understand why you think this is a good idea? I'm guessing it's because things with kind Type/(Type ->)* Type usually aren't that interesting at the kind-level? I get that we have naming conventions for things (e.g. m usually is a monad with kind (Type -> Type)), but I think including these types of kind signatures may actually make some things more readable.

Consider the ComponentSpec type from Halogen:

-- I believe the kind signature for `ComponentSpec` is the following.
type ComponentSpec :: Type -> (Type -> Type) -> Type -> Row Type -> Type -> Type -> (Type -> Type) -> Type
type ComponentSpec state query action slots input output m = 
  { eval :: (HalogenQ query action input) ~> (HalogenM state action slots output m)
  , initialState :: input -> state
  , render :: state -> HTML (ComponentSlot slots m action) action 
  }

I believe this type should have its kind signature displayed in its docs only because of the Row Type kind for slots. For argument's sake, let's pretend the slots type parameter doesn't exist. By following the proposed rule above, a version of ComponentSpec without slots would not have its kind signature displayed in the docs. In other words, it would look like this:

type ComponentSpec state query action input output m = 
  { eval :: (HalogenQ query action input) ~> (HalogenM state action output m)
  , initialState :: input -> state
  , render :: state -> HTML (ComponentSlot m action) action 
  }

Without the kind signature, I as a hypothetical reader who is unfamiliar with this library would have to look at the usages of each type parameter in the record to figure out what its kind is. Some are pretty obvious (e.g. input and state must be of kind Type). Others follow convention (i.e. m is probably a monad). Others (e.g. query, action, output) are not obvious because they are used in some other type (e.g. HalogenQ, HalogenM, etc.). I would have to look at those types to figure that out.

On another hand, this PR in its current state solves the problem of displaying kind signatures and their comments in the resulting documentation (pending discussion on whether the test for inferred kind signatures should 'pass' or not). What you've proposed above sounds like an optimization that can be built on top of the work I've done here.

Assuming we do decide to implement the proposed idea above, wouldn't it make better sense to implement it in a separate PR? Correct me if I'm wrong, but wouldn't the implementation entail pattern matching on the P.Type () data and figuring out whether the declKind of a Declaration should be updated with the inferred kind or not. If done wrongly, it might remove kind signatures that we'd otherwise want in the docs. So, to me, it seems like that will be a lot of work itself and would be best done in a PR separate from this one.

@rhendric
Copy link
Copy Markdown
Member

Could you help me understand why you think this is a good idea?

I totally didn't explain my abuse of syntax, but what I meant was: omit kinds when they are Type, Type -> Type, Type -> Type -> Type, etc., but leave the kind signatures in for, e.g., (Type -> Type) -> Type.

Equivalently, omit kind signatures when every type parameter has kind Type.

Hopefully that makes a little more sense on the face of it. I'm not worried about confusion because, if the kind signature is missing, the arity of the type constructor is enough to reconstruct the entire kind.

Maybe it should be its own PR, but I think Type -> Type -> Type signatures are going to add a lot of noise and I'm skeptical about the net value of documenting inferred kinds unless something is done about that.

@hdgarrood
Copy link
Copy Markdown
Contributor

I agree that kind signatures are probably just noise in the case where every parameter has kind Type, and including those is going to be noisy and distracting, especially for users who are just beginning with PureScript and haven't come across kinds yet. How about, to keep the scope of this PR small, we go back to only including explicit kind signatures for now, and then in a later PR, we could start inserting inferred kinds according to @rhendric's suggestion - only when not every parameter has kind Type?

@hdgarrood
Copy link
Copy Markdown
Contributor

hdgarrood commented Jun 20, 2021

As for the inferred kind signatures like forall (k :: Type). k -> Type, I think we should strip the :: Type (the 'sort annotation' for k?) if the sort is Type, so that it comes out in the docs as forall k. k -> Type. (Aside: I'm not sure if it is possible to write a data type whose inferred kind has any sort other than Type. @natefaubion do you know?) But that can also happen in a follow-up PR, especially if we are going to hold off on inserting inferred kinds until a subsequent PR.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

Equivalently, omit kind signatures when every type parameter has kind Type.

Ah... I was confusing your idea with something else entirely. I agree with the above idea. Restating it for myself, if I have a type like the following:

-- inferred kind signature
-- type Foo :: Type -> Type -> Type -> Type
type Foo a b c = Either a (Either b c)

we wouldn't display the kind signature for Foo because a, b, and c all have kind Type. Displaying the kind signature would be unnecessarily verbose and only distract the reader. In cases like the HalogenQ/HalogenM issue above, we would know what the type parameters' kinds are as either because they are displayed (e.g. because other type parameters' kinds would be something other than just Type) or because they are not displayed (i.e. they are all kind Type). Either way, the reader would know.

Still, if we take this approach, we might want to update the Pursuit help pages to clarify this point. Otherwise, a reader would only know of this fact if they looked at the release notes of the PS version that would include this change.

How about, to keep the scope of this PR small, we go back to only including explicit kind signatures for now, and then in a later PR, we could start inserting inferred kinds according to rhendric's suggestion - only when not every parameter has kind Type?

Sounds good.

, declChildren = []
, declInfo = info
, declKind = Nothing -- kind sigs are added in augment pass
, declKind = Nothing -- explicit kind signatures are added in augment pass
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The treatment of explicit versus inferred kind signatures is irrelevant to this piece of code, so I think it shouldn’t care about the distinction - especially because when we do update this to include inferred kind signatures, it’s going to be very easy to forget to update this comment, because the code in this function isn’t going to have to change. I’d suggest leaving this comment as it was before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I've removed the commit that made that change.

Copy link
Copy Markdown
Contributor

@hdgarrood hdgarrood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks for picking this up.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

@hdgarrood Thanks! One quick question. I noticed that the changelog doesn't really explain this feature. Should I merge this as is? Or further explain what we changed here in the release notes?

The current changelog just says

I was thinking of writing this:


  • Display kind signatures and their comments in documentation (Display kind signatures and comments in docs #4100 by JordanMartinez)

    Previously, data/newtype/type/class declarations that have explicit kind
    signatures would not display those kind signatures in their documentation.
    For example, the two below types...

    data PolyProxy :: forall k. k -> Type
    data PolyProxy a = PolyProxy
    
    data TypeProxy :: Type -> Type
    data TypeProxy a = TypeProxy

    ... would only show the following information in their docs. One cannot
    be distinguished from another due to the missing kind signatures:

    data PolyProxy a = PolyProxy
    
    data TypeProxy a = TypeProxy
    

    Now, these types' kind signatures are displayed above their declarations
    in their docs, similar to what one would see in the source code.

@hdgarrood
Copy link
Copy Markdown
Contributor

Yeah, I think that sounds good.

@JordanMartinez
Copy link
Copy Markdown
Contributor Author

Huh... CI on Mac failed with this error. I'll restart the build.

   purescript    > bower purescript-either#5.0.0                        ECMDERR Failed to execute "git ls-remote --tags --heads https://github.com/purescript/purescript-either.git", exit code of #128 fatal: unable to access 'https://github.com/purescript/purescript-either.git/': Failed to connect to github.com port 443: Operation timed out
  purescript    > 
  purescript    > Additional error details:
  purescript    > fatal: unable to access 'https://github.com/purescript/purescript-either.git/': Failed to connect to github.com port 443: Operation timed out
  purescript    > 

@JordanMartinez JordanMartinez merged commit fcf0acb into purescript:master Jun 22, 2021
@JordanMartinez JordanMartinez deleted the fixDocsOfTypesWithKindSigs branch June 22, 2021 03:08
rhendric added a commit to rhendric/purescript that referenced this pull request Jul 3, 2021
rhendric added a commit to rhendric/purescript that referenced this pull request Jul 3, 2021
rhendric added a commit to rhendric/purescript that referenced this pull request Jul 3, 2021
rhendric added a commit to rhendric/purescript that referenced this pull request Jul 6, 2021
rhendric added a commit to rhendric/purescript that referenced this pull request Jul 11, 2021
rhendric added a commit to rhendric/purescript that referenced this pull request Jul 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include kind information in HTML docs

3 participants