Proposal #606: small syntax change around types in terms; recommendation: vote

Proposal #606 https://github.com/ghc-proposals/ghc-proposals/pull/606 has been submitted to the committee, by Vlad. This proposal makes a tiny change. Suppose we have `f :: forall (x :: Type) -> x -> ...` (this is part of -XRequiredTypeArguments). Then (as proposed) we can write `y = f (type Int -> Int) abs ...`. The `type` keyword here is to signal that what comes is a type: it should be parsed as a type and name-resolved as a type. This might matter if there is, say, a constructor Int in scope. The proposal at hand is whether to accept the above program or to require extra parentheses, thusly: `y = f (type (Int -> Int)) abs ...`. That's it -- that's the entire proposal. (It says we should _not_ accept the former, without parentheses.) The motivation is to avoid surprising users. Normally when we have `word1 word2 -> word3`, word1 and word2 will associate more closely than the arrow. Yet the first example has `type Int -> Int` where the arrow binds more tightly. Because `type` is a keyword, this is no challenge to parse and is not ambiguous -- it's just perhaps confusing to users. There was some debate in the proposal, but in my perusal, not a clear indication toward any particular direction. The most rigorous statement I could find is that the new syntax is a subset of the original, and so we can easily reverse this decision later. I propose we vote on the matter. We have two choices: 1. Original syntax: allow `(type Int -> Int)` as an argument. 2. Amended syntax: require `(type (Int -> Int))` as an argument. -------------------------------- My vote: 1. There are many keywords in Haskell, and we are used to parsing these differently. For example, if we have `f (do x <- blah ...)`, we know quite well that the <- is within the `do`, not the other way around. Ditto `case`: we don't require scrutinees to be parenthesized. I posit that the strangeness some have felt around `(type Int -> Int)` is (understandable) confusion in the face of novelty. But the `type` herald will not be novel forever, and I think we'll enjoy having fewer parentheses in our code in the long run. (I might be arguing for "2 today, then 1 tomorrow". But let's just skip the intermediate step and do 1 now.) I welcome your opinions and votes. It would be great to conclude this in the next 2 weeks, by Feb 7. Thanks! Richard

Maybe I'm misreading this, but this can potentially break existing code,
can it not?
On Thu, 25 Jan 2024 at 02:47, Richard Eisenberg
Proposal #606 https://github.com/ghc-proposals/ghc-proposals/pull/606 has been submitted to the committee, by Vlad.
This proposal makes a tiny change. Suppose we have `f :: forall (x :: Type) -> x -> ...` (this is part of -XRequiredTypeArguments). Then (as proposed) we can write `y = f (type Int -> Int) abs ...`. The `type` keyword here is to signal that what comes is a type: it should be parsed as a type and name-resolved as a type. This might matter if there is, say, a constructor Int in scope. The proposal at hand is whether to accept the above program or to require extra parentheses, thusly: `y = f (type (Int -> Int)) abs ...`. That's it -- that's the entire proposal. (It says we should _not_ accept the former, without parentheses.)
The motivation is to avoid surprising users. Normally when we have `word1 word2 -> word3`, word1 and word2 will associate more closely than the arrow. Yet the first example has `type Int -> Int` where the arrow binds more tightly. Because `type` is a keyword, this is no challenge to parse and is not ambiguous -- it's just perhaps confusing to users.
There was some debate in the proposal, but in my perusal, not a clear indication toward any particular direction. The most rigorous statement I could find is that the new syntax is a subset of the original, and so we can easily reverse this decision later.
I propose we vote on the matter. We have two choices:
1. Original syntax: allow `(type Int -> Int)` as an argument. 2. Amended syntax: require `(type (Int -> Int))` as an argument.
--------------------------------
My vote: 1.
There are many keywords in Haskell, and we are used to parsing these differently. For example, if we have `f (do x <- blah ...)`, we know quite well that the <- is within the `do`, not the other way around. Ditto `case`: we don't require scrutinees to be parenthesized. I posit that the strangeness some have felt around `(type Int -> Int)` is (understandable) confusion in the face of novelty. But the `type` herald will not be novel forever, and I think we'll enjoy having fewer parentheses in our code in the long run. (I might be arguing for "2 today, then 1 tomorrow". But let's just skip the intermediate step and do 1 now.)
I welcome your opinions and votes. It would be great to conclude this in the next 2 weeks, by Feb 7.
Thanks! Richard _______________________________________________ ghc-steering-committee mailing list ghc-steering-committee@haskell.org https://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-steering-committee

On 25/01/2024 06:14, Moritz Angermann wrote:
Maybe I'm misreading this, but this can potentially break existing code, can it not?
I don't think this has been released yet, because RequiredTypeArguments is coming as part of GHC 9.10, and the fork is due in about a month. So there's no existing code to break and we can freely choose at this point, provided the decision is made in good time. Of course if 9.10 ships with option 1 and we later switch to option 2, that would be a breaking change. But it looks like HEAD might currently have option 2 implemented anyway? As to the proposal overall, I'm rather on the fence. Adam
On Thu, 25 Jan 2024 at 02:47, Richard Eisenberg
mailto:rae@richarde.dev> wrote: Proposal #606 https://github.com/ghc-proposals/ghc-proposals/pull/606 has been submitted to the committee, by Vlad.
This proposal makes a tiny change. Suppose we have `f :: forall (x :: Type) -> x -> ...` (this is part of -XRequiredTypeArguments). Then (as proposed) we can write `y = f (type Int -> Int) abs ...`. The `type` keyword here is to signal that what comes is a type: it should be parsed as a type and name-resolved as a type. This might matter if there is, say, a constructor Int in scope. The proposal at hand is whether to accept the above program or to require extra parentheses, thusly: `y = f (type (Int -> Int)) abs ...`. That's it -- that's the entire proposal. (It says we should _not_ accept the former, without parentheses.)
The motivation is to avoid surprising users. Normally when we have `word1 word2 -> word3`, word1 and word2 will associate more closely than the arrow. Yet the first example has `type Int -> Int` where the arrow binds more tightly. Because `type` is a keyword, this is no challenge to parse and is not ambiguous -- it's just perhaps confusing to users.
There was some debate in the proposal, but in my perusal, not a clear indication toward any particular direction. The most rigorous statement I could find is that the new syntax is a subset of the original, and so we can easily reverse this decision later.
I propose we vote on the matter. We have two choices:
1. Original syntax: allow `(type Int -> Int)` as an argument. 2. Amended syntax: require `(type (Int -> Int))` as an argument.
--------------------------------
My vote: 1.
There are many keywords in Haskell, and we are used to parsing these differently. For example, if we have `f (do x <- blah ...)`, we know quite well that the <- is within the `do`, not the other way around. Ditto `case`: we don't require scrutinees to be parenthesized. I posit that the strangeness some have felt around `(type Int -> Int)` is (understandable) confusion in the face of novelty. But the `type` herald will not be novel forever, and I think we'll enjoy having fewer parentheses in our code in the long run. (I might be arguing for "2 today, then 1 tomorrow". But let's just skip the intermediate step and do 1 now.)
I welcome your opinions and votes. It would be great to conclude this in the next 2 weeks, by Feb 7.
Thanks! Richard
-- Adam Gundry, Haskell Consultant Well-Typed LLP, https://www.well-typed.com/ Registered in England & Wales, OC335890 27 Old Gloucester Street, London WC1N 3AX, England

I keep going back and forth. But
I vote 1
On the grounds that:
- The extra parentheses would almost always be needed, 2 is probably an
unreasonable amount of syntactic noise
- We write `type Maybe a = …` not `type (Maybe a) = …`. So there's more
symmetry with the declaration in 1.
Yet, like Simon and Joachim, it took me a second to parse `f (type Maybe
Int)` correctly.
On Thu, 25 Jan 2024 at 07:14, Moritz Angermann
Maybe I'm misreading this, but this can potentially break existing code, can it not?
On Thu, 25 Jan 2024 at 02:47, Richard Eisenberg
wrote: Proposal #606 https://github.com/ghc-proposals/ghc-proposals/pull/606 has been submitted to the committee, by Vlad.
This proposal makes a tiny change. Suppose we have `f :: forall (x :: Type) -> x -> ...` (this is part of -XRequiredTypeArguments). Then (as proposed) we can write `y = f (type Int -> Int) abs ...`. The `type` keyword here is to signal that what comes is a type: it should be parsed as a type and name-resolved as a type. This might matter if there is, say, a constructor Int in scope. The proposal at hand is whether to accept the above program or to require extra parentheses, thusly: `y = f (type (Int -> Int)) abs ...`. That's it -- that's the entire proposal. (It says we should _not_ accept the former, without parentheses.)
The motivation is to avoid surprising users. Normally when we have `word1 word2 -> word3`, word1 and word2 will associate more closely than the arrow. Yet the first example has `type Int -> Int` where the arrow binds more tightly. Because `type` is a keyword, this is no challenge to parse and is not ambiguous -- it's just perhaps confusing to users.
There was some debate in the proposal, but in my perusal, not a clear indication toward any particular direction. The most rigorous statement I could find is that the new syntax is a subset of the original, and so we can easily reverse this decision later.
I propose we vote on the matter. We have two choices:
1. Original syntax: allow `(type Int -> Int)` as an argument. 2. Amended syntax: require `(type (Int -> Int))` as an argument.
--------------------------------
My vote: 1.
There are many keywords in Haskell, and we are used to parsing these differently. For example, if we have `f (do x <- blah ...)`, we know quite well that the <- is within the `do`, not the other way around. Ditto `case`: we don't require scrutinees to be parenthesized. I posit that the strangeness some have felt around `(type Int -> Int)` is (understandable) confusion in the face of novelty. But the `type` herald will not be novel forever, and I think we'll enjoy having fewer parentheses in our code in the long run. (I might be arguing for "2 today, then 1 tomorrow". But let's just skip the intermediate step and do 1 now.)
I welcome your opinions and votes. It would be great to conclude this in the next 2 weeks, by Feb 7.
Thanks! Richard _______________________________________________ ghc-steering-committee mailing list ghc-steering-committee@haskell.org https://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-steering-committee
_______________________________________________ ghc-steering-committee mailing list ghc-steering-committee@haskell.org https://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-steering-committee
-- Arnaud Spiwack Director, Research at https://moduscreate.com and https://tweag.io.

I vote (2), fairly strongly Remember this comment from Vlad: @phadej https://github.com/phadej For what it's worth, this was my
initial thinking when I put ktype there, so past me agrees with you. What changed my opinion is that two years later (i.e. now) I looked at the examples like *fn (type Int -> [a])* and had to double check the specification if it was supposed to be fn ((type Int) -> [a]) or fn (type (Int -> [a])).
The point is that the `type` namespace changer can appear *deep within a type*. It's not like "@"! For exmaple fn ((type K) -> [a]) makes perfect sense. fn has a required type argument, but in the (type K) *sub-part *of the type do we switch to the type namespace. (Maybe K is in scope also as a data constructor.) Without the parens, do you really want to wonder about how this parses? fn (type K -> [a]) I prefer code that is slightly longer, but much clearer, than saving two characters but requiring reference to the user manual to parse. Let's make it simple and unambiguous for now. If it seems painful in practice we can debate liberalising it. Simon

I remain unmoved by Simon's example. The status quo is already simple: `type` extends as far to the right as possible. Just like \. And just like `do`, `else`, and `in`. The hope is that people using this will tend to avoid punning, and so the matter will be moot, anyway. Richard On Thu, Jan 25, 2024 at 7:08 AM Simon Peyton Jones < simon.peytonjones@gmail.com> wrote:
I vote (2), fairly strongly
Remember this comment from Vlad:
@phadej https://github.com/phadej For what it's worth, this was my
initial thinking when I put ktype there, so past me agrees with you. What changed my opinion is that two years later (i.e. now) I looked at the examples like *fn (type Int -> [a])* and had to double check the specification if it was supposed to be fn ((type Int) -> [a]) or fn (type (Int -> [a])).
The point is that the `type` namespace changer can appear *deep within a type*. It's not like "@"! For exmaple fn ((type K) -> [a]) makes perfect sense. fn has a required type argument, but in the (type K) *sub-part *of the type do we switch to the type namespace. (Maybe K is in scope also as a data constructor.) Without the parens, do you really want to wonder about how this parses? fn (type K -> [a])
I prefer code that is slightly longer, but much clearer, than saving two characters but requiring reference to the user manual to parse.
Let's make it simple and unambiguous for now. If it seems painful in practice we can debate liberalising it.
Simon _______________________________________________ ghc-steering-committee mailing list ghc-steering-committee@haskell.org https://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-steering-committee

I've been on the fence, as have others here. Simon PJ describes himself as being far from the fence -- and in the same place as Vlad, our proposer and intrepid implementer. So while I'm not deeply convinced in my soul about this, I think the right choice as a committee is to go with option (2) in this thread (which is what the proposer is asking for). I don't see much of a reason to keep extending this debate (everyone who is saying they care strongly is getting their way), but out of courtesy let's extend this to the end of the month (one extra day this year!). If I don't hear further comments, I'll accept this on Thursday. Thanks! Richard
On Jan 25, 2024, at 7:07 AM, Simon Peyton Jones
wrote: I vote (2), fairly strongly
Remember this comment from Vlad:
@phadej https://github.com/phadej For what it's worth, this was my initial thinking when I put ktype there, so past me agrees with you. What changed my opinion is that two years later (i.e. now) I looked at the examples like fn (type Int -> [a]) and had to double check the specification if it was supposed to be fn ((type Int) -> [a]) or fn (type (Int -> [a])).
The point is that the `type` namespace changer can appear deep within a type. It's not like "@"! For exmaple fn ((type K) -> [a]) makes perfect sense. fn has a required type argument, but in the (type K) sub-part of the type do we switch to the type namespace. (Maybe K is in scope also as a data constructor.) Without the parens, do you really want to wonder about how this parses? fn (type K -> [a])
I prefer code that is slightly longer, but much clearer, than saving two characters but requiring reference to the user manual to parse.
Let's make it simple and unambiguous for now. If it seems painful in practice we can debate liberalising it.
Simon _______________________________________________ ghc-steering-committee mailing list ghc-steering-committee@haskell.org https://mail.haskell.org/cgi-bin/mailman/listinfo/ghc-steering-committee
participants (6)
-
Adam Gundry
-
Arnaud Spiwack
-
Moritz Angermann
-
Richard Eisenberg
-
Richard Eisenberg
-
Simon Peyton Jones