-
Notifications
You must be signed in to change notification settings - Fork 269
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
Explicit specificity #99
Conversation
ROTFL
|
I think this is a grand idea. I'm still a bit uncomfortable with allowing variables to be specified when they're not explicitly quantified, as in p :: a -> b -> F a b -- p @A @B is valid
q :: forall (a :: k). a -> G a -- p @K @A is valid I would generally prefer to see this effect from p :: forall a b. F a b
q :: forall k (a :: k). a -> G a but I recognize that being liberal about what's specified is helpful when using functions that weren't specifically written for |
I think this isn't completely future-compatible? This might be valid someday: {-# LANGUAGE NamedFieldPuns #-}
data Foo = Foo { i :: Natural }
forall (Foo { i } :: Foo) . Vec a i -> Vec a i |
@Ericson2314 That strikes me as somewhat surprising hypothetical syntax. Why use that instead of forall a (foo :: Foo). foo ~ 'Foo { i } => Vec a i -> Vec a i I mean, I guess you could argue that your way reads better, but it seems like unnecessary complication. |
I like this proposal. Internally, GHC already has "inferred" and "specified", but it's just inaccessible to the user. (Mind you, I always trip over nomenclature.) The "Proposed specification" is not nearly precise enough. Beyond syntax I think we need to say
both
We should also specify what happens in data/class declarations. Under "Motivation", consider this:
In today's GHC, with
and now in a visible type application I'd have to specify the kind argument, which I might not want to. This proposal makes it possible to bind |
Will there still be a way to specify kind parameter if needed? If I want to apply |
I chose my example for a reason :). It's unclear whether |
However, the overall design raises a question. We appear to have three levels of visibility for a parameter: visible, invisible, and super-invisible (inferred). Is there no way whatsoever to specify the super-invisible parameters? Is this asking for a Perhaps we would want visibility levels?
etc |
@treeowl: There was some debate about whether or not to make user-written, implicitly quantified variables available for type application. We decided "yes" in the end, but this was not an obvious choice, at the time or in retrospect. I still favor this approach as it vastly enlarges the applicability of visible type application, but I won't defend the choice staunchly. @Ericson2314: That's an interesting new syntax. Let me expand your idea a bit. In some hypothetical language that came before Haskell (cough, cough, ML), we can imagine that pattern matching must be done explicitly:
But in Haskell, we can pattern-match directly on function arguments:
How convenient. You seem to be proposing (in some far-off future) that we do something similar in type variable binders. Just as the difference between my snippets above is that the latter allows pattern-matching in a variable binding position, you want to do the same, but in types. It's a reasonable idea. And if we allow type patterns to appear in type variable binding positions, I agree that the proposed syntax is indeed not future-compatible. I'm not sure how worried to be about this, but I'm glad you brought it up. Do you have any other syntax ideas? Regardless, I've added this concern to the proposal. @simonpj: I've added a note to the specification in the proposal that braced variables work with @vagarenko: No. If a variable is inferred, then it's not available with visible type application. Full stop. This fact keeps type inference predictable. The idea is that inferred type variables are invented by GHC, and it's conceivable that different minor versions of GHC might do it slightly differently. If there were a way to observe the ordering of inferred variables, then programs could break. Of course, these new braced variables aren't really inferred in the same way (instantiating them would be predictable), but allowing an override here but not for GHC-inferred variables leads to a system where we have specified, user-written-inferred, and truly-inferred variables in the mix, and we'd be teetering toward madness. @int-index: You've anticipated parts of my answer to @vagarenko. No, this doesn't lead to three levels of visibility: just two. They are specified and inferred. That's it! No |
Okay, so let us consider the visibility levels model I mentioned earlier:
In this model, we could also talk about infinity-level (inferred) — those variables can be introduced by GHC exclusively, and there is no possibility for an override. After this proposal we have an asymmetry: the users can introduce the inferred variables, but cannot override them. This is not a deal breaker, but the abstraction is leaking. |
What abstraction is leaking? I see an asymmetry, but I don't see a leaky abstraction. |
The abstraction (to my understanding) is that the "inferred" level is an implementation detail for GHC. Now it's leaking to the user-facing language (the ability to write (Once again, I'm in favor of the proposal, I see the need for it, just sharing an observation that bothers me a bit) |
OK -- I can see what you're getting at now, and I see why it's bothersome. But I don't think we can do better. |
Can we ensure that the variables introduced by the user with the |
Since GHC infers these type variables, this is similar to partial type signatures, which GHC infers also. In the motivation section, we have this example:
With
And then introduce named partial type signatures, which allow access to whatever GHC infers:
(the syntax is hypothetical) Now |
But I didn't use visible type application for the inferred variable in my example:
I use type application for
|
@int-index: You are suggesting replacing
with
where the Bottom line: I don't think this is an adequate replacement for this proposal. @vagarenko: Ah, I understand better now. Yes, that would still work. This proposal is all about how to write types to work the desired way with visible type application. |
Well, that's the point (sort of). Rather than add another mechanism to introduce variables, we just use "whatever GHC infers", which might not be a variable (I don't see how it's problematic).
That's good news, thanks!
Okay, but can you add it to alternatives? There's probably a use case where this alternative wouldn't work, but I can't come up with one. |
What's the argument for adding this syntax instead of allowing to declare type variables out of dependency order natively? With that, no special syntax would be needed, and you could effectively "hide" type variables just by putting them last. It seems to me like support for that would only require a trivial amount of sugar on top of what this proposal does. At least this example made it seem that way: typeRep4 :: forall {k} (a :: k) k'. (k ~ k', Typeable a) => TypeRep a So what if you could just write that as below? typeRep4 :: forall (a :: k) k. Typeable a => TypeRep a Internally it could be implemented using the proposed hiding mechanism without having to expose that as a language feature. Some care has to be taken to not expose the
|
That's nice, but anti-modular. Say you are using a type alias:
Admittedly, this came out more contrived than I thought, but the basic idea is if some quantifiers are abstracted away, it's not easy to rearrange them. |
Overall, I am sort of getting the same cat-and-mouse vibes between introduction and elimination as @int-index with the |
I'm intrigued by @Bj0rnen's idea. It can be backward-compatible: as long as a library doesn't change existing type parameters, that library can always add new ones at the end of a list. I'm not convinced by the anti-modular argument @Ericson2314 puts forward. Implementing this idea seems very difficult, unfortunately. (Currently, GHC uses the same types in Core as it does in Haskell, and @Bj0rnen's idea means we either have to stop doing this or somehow encode a transformation between them.) I'd be curious for more feedback on this idea. |
I've added @Bj0rnen's idea as an alternative, and I argue there why I think the main proposal is still the best way forward. |
@goldfirere What's a use case that scoped
|
I see @yav has posted some (useful) questions for clarification about how the proposal works on the Committee mailing list. Could I ask that such questions and their answers appear in this discussion thread. (And possibly get incorporated into the proposal text, if it's unclear/incomplete.) I appreciate it's often difficult to separate out aspects of the discussion but as a rule of thumb: here is about the what and how; the Committee discussion should be only about the why and why important/worthwhile, and assuming those Committee members who post have already absorbed the proposal. (At least, that's how I understand it.) |
It still bothers me that, assuming we get
This means that we have a language mechanism to override visibility at call site, and then another mechanism to say "no, this variable visibility cannot be overriden". I fail to see why we stop at this point and do not introduce the next level, "yes, I do want this variable visibility to be overriden", ad infinitum. This leads us to the design I outlined in #99 (comment) As long as we don't introduce I would like to avoid committing to the current suboptimal solution by exposing its syntax to the user. |
It seems to me that we are more or less forced into this design:
I suppose that we could declare class (3) empty, by rejecting any typethat has variables that are not explicitly mentioned, and thus live in class (3). So then
would not be poly-kinded -- instead I suppose it'd default to kind
And I suppose that might not be a bad thing. But what about term-level functions whose type is inferred? (A bit like |
I think we have a general (but probably informal) rule that if you want to use 'fancy' type features, you must put a type signature. This is in line with Hindley-Milner inference, which never requires a signature.
Yes and yes. Speaking as somebody still trying to get my head round type application, I always feel nervous about the canonical sequence of arguments. Having to guess it for tokens that don't even have a signature strikes me as too hard. As it is, you have to pay attention it's not as simple as left-to-right appearance of variables in a signature. Then you're implying a rule that seems sensible: to use type application, put a signature; if that's poly-kinded type application, that signature must mention variables for the kinds. |
Yes, that's the rule today. int-index is sad to have three categories, and I sympathise. But I'm just pointing out that it's hard to make do with two. If anyone has an idea for how to make do with two, that would be great: let's see it. |
(Must admit I've lost the plot on all the options swirling around here; and I've just read the 'Type Variables in Patterns' paper ...) Can't we resort to type signatures in patterns, and thereby 'reach' any type/kind, even if we can't reach it via type applications? To take your
(I've used distinct The TVIP paper points out pattern signatures can be clumsy/ugly. (Presumably this arises because the code wanting to put signatures is importing something not declared with type applications in mind.) There is a case [paper section 3.6] where it's impossible to specify a suitable pattern signature. But the paper's proposing a way to express that with type applications. It's an existential GADT with a type family; so falls well within my 'fancy' features; then there has to be a signature. (And hooray for doing away with |
I've updated this proposal to remove the ability to put braces in the tyvars in a class head, in favor of new proposal #148. |
I am still undecided whether I like this approach (encoding specificity in the type) or would rather see that as separate declarations (like we do for the roles of type constructor). But I think I know what questions I have to ask to make up my mind: Will the following work:
If the answer is “yes”, then I agree with putting the specificity into the types, because it behaves like types. If the answer is “no”, ans specificity is really a property of “the I could not answer these questions clearly from the spec, which may mean that the spec could be refined to do so. (Or I missed the crucial bit if its there, which is possible :-)) NB: Coq has both forms of declarations ( |
In response to #99 (comment)
In other words, these really are types, not some other construct. |
Thanks! |
I appreciate this proposal as well. Just today I really needed the feature. I will describe my use-case below. Problem descriptionI have data type parameterized by type variable polymorphic in kind: data TypeRepMap (f :: k -> Type) ... And I would like to write the following code: lookup :: forall a f . TypeRepMap f -> Maybe (f a)
lookup = ... some implementation here ...
member :: forall a f . TypeRepMap f -> Bool
member trMap = case lookup @a trMap of
Nothing -> False
Just _ -> True Unfortunately, this code doesn't compile with the following error:
Workaround 1Change type of member :: forall k (a :: k) (f :: k -> Type) . TypeRepMap f -> Bool I don't like this workaround because every time I call member @_ @Int instead of just Workaround 2 (current solution)This solution was proposed by @int-index. It makes interface better but messes up with documentation type KindOf (a :: k) = k
member :: forall a (f :: KindOf a -> Type) . TypeRepMap f -> Bool Maybe somebody knows better solution? If current proposal is implemented then I can just write the following (without introducing custom types): member :: forall {k} (a :: k) (f :: k -> Type) . TypeRepMap f -> Bool |
The proposal has been accepted; the following discussion is mostly of historic interest.
This proposal introduces new syntax
typeRep :: forall {k} (a :: k). ...
(the braces are new) to allow a user to quantify a variable without affecting downstream users who might use visible type application.Rendered