-
Notifications
You must be signed in to change notification settings - Fork 298
Explain rows in typeclass instances #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I don't know how to answer this either, but there are a couple of things about that that don't quite seem right to me. Firstly, you start by saying that rows cannot appear in instance heads, but then none of your examples actually show an attempt to define an instance with a row in the instance head, which I think is a bit confusing. Is there more to the restrictions on rows in instances than just this? Then, your last paragraph which seems to be the 'why' that we've been building up to is a little unsatisfying. You say
I think this is true but irrelevant. Even if I do attempt to define such an instance in the same module as the class it still doesn't work: module Main where
class C a
instance a :: C () which fails with "Type class instance head is invalid due to use of type () " and then,
I think it's better to say that Finally, just saying "it's untenable" is not really an explanation at all; I think this needs to either be expanded or removed. |
Unfortunately I don't know how to address these things 😅 we'll have to wait for someone else to weigh in I think (perhaps @LiamGoodacre, @paf31, or @garyb?) |
Yeah, the "why" is still a bit unclear to me. Rather than having nothing for that, though, I thought it better to make a poor guess to encourage the answer to appear from reviewers. 😅 I think that answer is in the right ballpark, but it's poorly explained/justified. I need to find better examples, yeah, and actually test them. The point of this entire section wasn't entirely clear, and good examples would support the point. Your idea is pretty good - I'll put that in there first, then do the Record stuff after. Another question this could answer - do Kinds of types restrict their instances? Another thing to test before adding in there. |
Yes, I'm pretty certain that all type class parameters are restricted to a single kind. You can write e.g. class A (t :: Type -> Type) to say that the parameter of |
So I should clarify: if you write |
Also I just checked and class A (a :: # Type)
instance inst :: A r is fine. Note that |
I've added a section on Kinds - can split it into a separate PR if desired. I haven't tested the claims it makes - can do later, after we settle on points to make. Nice find, @hdgarrood - I've updated to remove statements saying rows aren't allowed - it's only concrete rows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made some more comments but before you go off and fix these let's wait for someone who knows more about this to weigh in; we might not really be looking at this from the right direction I think.
language/Type-Classes.md
Outdated
-- `e` is kind `# Type` | ||
``` | ||
|
||
If a typeclass's parameter's Kind is `Type`, an instance cannot be made for a type of kind `# Type` or `Type -> Type`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like there's special casing going on but I don't think there is. I think you should just say something like "the kinds of any types mentioned in an instance declaration must match the kinds of those parameters as determined by the class's definition".
language/Type-Classes.md
Outdated
@@ -80,27 +80,50 @@ In fact, a type similar to this `AddInt` is provided in `Data.Monoid.Additive`, | |||
|
|||
For multi-parameter type classes, the orphan instance check requires that the instance is either in the same module as the class, or the same module as at least one of the types occurring in the instance. (TODO: example) | |||
|
|||
## Kinds | |||
|
|||
A typeclass's parameter can specify the Kind. If it doesn't specify the type's Kind, it can be inferred by methods in the class. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better style to use a lowercase 'k' for kind.
language/Type-Classes.md
Outdated
|
||
``` purescript | ||
class A a | ||
-- `a` is kind `Type` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about "without a kind signature, a
defaults to kind Type
"? and similar explanations for the other examples?
language/Type-Classes.md
Outdated
instance mClosedRow :: M () | ||
class M' (t :: # Type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to define two different classes in this example; just put the kind signature on M
I think.
I saw @LiamGoodacre added a thumbs-up to agree with your explanation about the relationship between typeclass parameter kinds and kinds of instance datatypes. I've updated the docs to improve and clarify this section. Should fact-check/review the "An effect of adjusting the arity of kinds..." paragraph there. We still need a better paragraph to explain why specific Rows aren't allowed in instances. I'd like it to be a technical or philosophical explanation. My test of using a specific row
|
I guess it must be because it would complicate solving too much. For example, what should the compiler do with the following? (assuming the proxy syntax which isn't in master just yet) class Awesome (a :: # Type) where
awesomeness :: @ a -> Int
instance awesomeFoo :: Awesome (foo :: Int | e) where
awesomeness _ = 0
instance awesomeBar :: Awesome (bar :: Int | e) where
awesomeness _ = 1
-- should this log "0" or "1"?
main = logShow $ awesomeness (@ (foo :: Int, bar :: Int)) |
language/Type-Classes.md
Outdated
@@ -80,6 +80,69 @@ In fact, a type similar to this `AddInt` is provided in `Data.Monoid.Additive`, | |||
|
|||
For multi-parameter type classes, the orphan instance check requires that the instance is either in the same module as the class, or the same module as at least one of the types occurring in the instance. (TODO: example) | |||
|
|||
## Kinds | |||
|
|||
A typeclass's type parameter can specify the kind of types it applies to. If it doesn't specify the type's kind, it can be inferred by the typeclass's methods. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We usually refer to type class
by two words.
How does this sound?
"Type class parameters do not need to be of kind Type
. To explicitly specify a different kind, the parameter can be annotated with a kind signature. Unless the kind of a type parameter is explicitly annotated, it will be implicitly inferred based on how the type parameter is referred to by the type class's method signatures."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a lot more clear. I'll add that.
I was also curious about "typeclass" and "type class". I'll update this document to use "type class" if @hdgarrood agrees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"type class" is fine, yeah. Currently there are 49 occurrences of that and only 3 of "typeclass" in this repo so let's stick with "type class".
language/Type-Classes.md
Outdated
|
||
Instance selection works by looking at the "head" types of each type class parameter. Functional dependencies also plays a role, but we'll not consider that for now to simplify the explanation. The "head" of a type is the top-most type constructor, for example the head of `Array Int` is `Array` and the head of `Record r` is `Record`. | ||
|
||
As rows and records can easily be conflated to refer to the same thing, their difference is important to note here. `Record` is a simple type constructor in PureScript and therefore can be a head in an instance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the word simple
fits in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's true. 👍
language/Type-Classes.md
Outdated
|
||
## Rows | ||
|
||
Concrete Row literals cannot appear in instances. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless the parameter is fully determined by functional dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you share some example of this? If using functional dependencies, you won't be using a row literal, will you? Isn't functional dependencies about declaring how type variables relate to other type variables? If so, you won't be relating to a row literal. Unless... you mean constraints like SubRow r (x :: Int, y :: Boolean)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example: http://try.purescript.org/?gist=b76f79d22a28a0e31ec5577504672db8&backend=core
class Ctors dt (ct :: # Type) | dt -> ct
data X = X0 Int | X1 String
instance ctorsX :: Ctors X ("X0" :: Int, "X1" :: String)
data Y = Y0 Boolean
instance ctorsY :: Ctors Y ("Y0" :: Boolean)
I don't see how explaining instance selection in the Rows section is relevant. The problem with rows in instances is not about being in head position - it's about row literals. Yeah? I'd like to remove this explanation, or move it to a different place, unless it more directly explains the rows-in-instances conflict. |
I moved the instance selection explanation to its own section in this document. |
language/Type-Classes.md
Outdated
`instance functorEither :: Functor (Either e) where ...` | ||
``` | ||
|
||
An effect of needing to adjust the kind of a data type to fit into a type class which applies to a lower arity kind, like with `Functor` and `Either` above, is that the behavior and laws of an instance will bias towards to the right-most type parameters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this sentence; I think it will only make sense to people who already understand what it's trying to explain. This topic is definitely worth addressing but I think we should do that separately from this PR.
language/Type-Classes.md
Outdated
|
||
## Rows | ||
|
||
Concrete Row literals cannot appear in instances unless it's concrete type is fully determined by functional dependencies. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's not quite clear what "it's" refers to here. Also "it's" means "it is"; for something belonging to "it" you should use "its".
language/Type-Classes.md
Outdated
-- Prints the following when executed. | ||
this is a record | ||
this is an array | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is right. For instance:
module Main where
import Prelude
import Data.Foldable (fold)
import TryPureScript
main = render $ fold $
[ p $ text $ describe [1, 2, 3]
, p $ text $ describe [1.0, 2.0, 3.0]
]
class Describe a where
describe :: a -> String
instance describe1 :: Describe (Array Int) where
describe _ = "I am an Array Int"
instance describe2 :: Describe (Array Number) where
describe _ = "I am an Array Number"
The compiler has had to look beyond the head of each instance to select it here.
language/Type-Classes.md
Outdated
@@ -80,6 +102,63 @@ In fact, a type similar to this `AddInt` is provided in `Data.Monoid.Additive`, | |||
|
|||
For multi-parameter type classes, the orphan instance check requires that the instance is either in the same module as the class, or the same module as at least one of the types occurring in the instance. (TODO: example) | |||
|
|||
## Kinds | |||
|
|||
Type class parameters do not need to be of kind Type. To explicitly specify a different kind, the parameter can be annotated with a kind signature. Unless the kind of a type parameter is explicitly annotated, it will be implicitly inferred based on how the type parameter is used in the type class's method signatures. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's much more common that the kind of a type class parameter can be inferred from the methods than not, and so I think it would make more sense to talk about how the kind can be inferred before mentioning that a kind annotation can be provided. Also I think the "implicitly" is redundant here (is there such a thing as "explicit inference")?
@hdgarrood I finally got around to addressing your comments on this one. |
There are still a few aspects of this which don't quite feel right to me, in particular the explanation of why we don't allow rows in instances. At this point I feel I really need to poke around inside the compiler to work out what is going on here and why. Unfortunately now that the semester has started again I probably won't be able to do that for quite some time. |
Add data type examples for a documentation project |
Closing due to inactivity. Also, I think the entire doc repo could be redesigned. So, this could likely be clarified then. |
The other day I received an explanation of records and rows in typeclass instances. I'd like to have this clearly explained in one place, so here's my attempt at that. It needs fact-checking.
Also, this doesn't incorporate
RowList
representation of a Row, which can be used to overcome this limitation of rows in instances. I don't know how that relates to this explanation.