-
Notifications
You must be signed in to change notification settings - Fork 28
SIP-XX Allow Partial Function Literals to be defined with Parentheses #113
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
Open
lihaoyi
wants to merge
18
commits into
scala:main
Choose a base branch
from
lihaoyi:partial-function-parens
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
4e265a2
.
lihaoyi ba0543b
.
lihaoyi d7fa176
.
lihaoyi 603fa01
.
lihaoyi 5cfb86f
.
lihaoyi 74e0521
.
lihaoyi 2e6ba60
.
lihaoyi 086be15
.
lihaoyi 253346b
.
lihaoyi 46ebb63
Update content/partial-function-parens.md
lihaoyi 1c6e823
.
lihaoyi 18fbc9b
.
lihaoyi eb95d20
.
lihaoyi b57baa1
.
lihaoyi 51d09ed
.
lihaoyi 2fa1272
.
lihaoyi 79465dc
.
lihaoyi 7a2f6ce
.
lihaoyi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
--- | ||
layout: sip | ||
permalink: /sips/:title.html | ||
stage: implementation | ||
status: under-review | ||
title: SIP-XX Allow Partial Function Literals to be defined with Parentheses | ||
--- | ||
|
||
**By: Li Haoyi** | ||
|
||
## History | ||
|
||
| Date | Version | | ||
|---------------|--------------------| | ||
| Aug 22nd 2025 | Initial Draft | | ||
|
||
## Summary | ||
|
||
This proposal is to allow parens `(...)` to be used instead of curly braces `{...}` | ||
when defining partial functions which have only one `case`: | ||
|
||
|
||
```scala | ||
Seq((1, 2), (3, 4)).collect(case (a, b) if b > 2 => a) // 3 | ||
``` | ||
|
||
Currently this syntax is disallowed: | ||
|
||
```scala | ||
scala> Seq((1, 2), (3, 4)).collect(case (a, b) if b > 2 => a) | ||
-- [E018] Syntax Error: -------------------------------------------------------- | ||
1 |Seq((1, 2), (3, 4)).collect(case (a, b) if b > 2 => a) | ||
| ^^^^ | ||
| expression expected but case found | ||
| | ||
| longer explanation available when compiling with `-explain` | ||
``` | ||
|
||
Partial functions with multiple `case` blocks should also be allowed to use parentheses | ||
: | ||
|
||
|
||
```scala | ||
Seq((1, 2), (3, 4)).collect( | ||
case (a, b) if b > 2 => a | ||
case _ => ??? | ||
) | ||
``` | ||
|
||
Multi-line `case` blocks should work with parens as well, just like multi-line function | ||
literals without `case` already work: | ||
|
||
```scala | ||
Seq((1, 2), (3, 4)).collect( | ||
case (a, b) => | ||
println(b) | ||
a | ||
) | ||
|
||
// This already works today | ||
Seq((1, 2), (3, 4)).collect( | ||
(a, b) => | ||
println(b) | ||
a | ||
) | ||
``` | ||
|
||
For consistency, we also allow parentheses to be used in `match` statements: | ||
|
||
```scala | ||
(1, 2) match ( | ||
case (a, b) if b > 2 => a | ||
case _ => ??? | ||
) | ||
``` | ||
|
||
And we should allow multi-`case` single-line partial functions, just like you can already do | ||
with `match` today: | ||
|
||
```scala | ||
Seq((1, 2), (3, 4)).collect{ case (a, b) if b > 2 => a case (a, b) if a > 2 => b } | ||
Seq((1, 2), (3, 4)).collect(case (a, b) if b > 2 => a case (a, b) if a > 2 => b) | ||
|
||
// This already works today | ||
(1, 2) match { case (a, b) if b > 2 => a case _ => ??? } | ||
``` | ||
|
||
|
||
Partial function literals should also be allowed to be defined without parentheses for | ||
single-line scenarios such as: | ||
|
||
```scala | ||
val partial: PartialFunction[(Int, Int), Int] = case (a, b) if b > 2 => a | ||
``` | ||
|
||
This delimiter-less syntax is similar to what is already allowed today in `catch` blocks: | ||
|
||
```scala | ||
try ??? | ||
catch case e: Exception => ??? | ||
``` | ||
|
||
|
||
## Motivation | ||
|
||
With Scala 3's [Optional Braces](https://docs.scala-lang.org/scala3/reference/other-new-features/indentation.html), | ||
and [SIP-44's Fewer Braces](https://docs.scala-lang.org/sips/fewer-braces.html), single-line | ||
partial functions are one of the only remaining places where curly braces are mandatory in Scala | ||
syntax. | ||
|
||
```scala | ||
// No way to write this without curlies | ||
Seq((1, 2), (3, 4)).collect{ case (a, b) if b > 2 => a } | ||
``` | ||
Needing to swap between parens and curlies also adds friction of converting a function to | ||
a partial function, which happens frequently: | ||
|
||
```scala | ||
Seq((1, 2), (3, 4)).map((a, b) => a) // OK | ||
Seq((1, 2), (3, 4)).collect(case (a, b) if b > 2 => a) // BAD | ||
Seq((1, 2), (3, 4)).collect{ case (a, b) if b > 2 => a } // OK | ||
``` | ||
|
||
This also currently causes visual messiness in method call chains where some single-line | ||
methods use parens and others use curlies: | ||
|
||
```scala | ||
Seq((1, 2), (3, 4), (5, 6)) | ||
.filter(_._1 < 5) // PARENS | ||
.collect{ case (a, b) if b > 2 => a } // CURLIES | ||
.reduce(_ + _) // PARENS | ||
``` | ||
|
||
In the syntax of other expressions, curly braces are only | ||
necessary to define "blocks" with multiple statements: `if`-`else`, `try`-`catch`-`finally`, | ||
`for`-`yield`, `do`-`while`, method calls like `foo()` etc. all allow you to replace curly braces | ||
with parentheses or elide them altogether using indentation. | ||
This is unlike other languages like Java that mandate curly braces in these syntactic constructs. | ||
Furthermore, in most expressions, Optional Braces means you do not have to write the curlies | ||
if you do not want to. | ||
|
||
This proposal brings partial functions in-line with the rest of Scala syntax, with the curly | ||
braces only being mandatory for multi-statement blocks, and made optional with Scala 3's | ||
Optional Braces. With this proposal, curly braces are _only_ for opening multi-statement blocks, | ||
_always_ in places that can be replaced by indentation-based blocks if the user wants to do so. | ||
They are no longer also incidentally tied to partial-function syntax as they were before. | ||
|
||
With this change, all the snippets below are now valid: we can see how the syntax of `()`, `{}`, | ||
or `.collect:` followed by indentation is now fully orthogonal to the partial function `case` | ||
expression within them, resulting in a much more regular syntax than before when parentheses | ||
were prohibited but the other syntaxes worked. | ||
|
||
```scala | ||
Seq((1, 2), (3, 4)).collect(case (a, b) if b > 2 => a) | ||
Seq((1, 2), (3, 4)).collect { case (a, b) if b > 2 => a } | ||
Seq((1, 2), (3, 4)).collect: | ||
case (a, b) if b > 2 => a | ||
|
||
Seq((1, 2), (3, 4)).collect( | ||
case (a, b) if b > 2 => a | ||
case (a, b) if a > 2 => b | ||
) | ||
|
||
Seq((1, 2), (3, 4)).collect { | ||
case (a, b) if b > 2 => a | ||
case (a, b) if a > 2 => b | ||
} | ||
|
||
Seq((1, 2), (3, 4)).collect: | ||
case (a, b) if b > 2 => a | ||
case (a, b) if a > 2 => b | ||
|
||
Seq((1, 2), (3, 4)).collect( | ||
case (a, b) => | ||
println(b) | ||
a | ||
) | ||
|
||
Seq((1, 2), (3, 4)).map((a, b) => a) | ||
Seq((1, 2), (3, 4)).map { (a, b) => a } | ||
Seq((1, 2), (3, 4)).map: | ||
(a, b) => a | ||
Seq((1, 2), (3, 4)).map: | ||
(a, b) => | ||
println(b) | ||
a | ||
|
||
Seq((1, 2), (3, 4)).collect( | ||
case (a, b) if b > 2 => | ||
println(b) | ||
a | ||
) | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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'd like to push back on this aspect of the proposal (the rest is great!). We do not allow multiple statements between parentheses, and for good reason, since some of the logic around newline insertion/indentation relies on this. For consistency, we should not allow multiple cases either. I see no good reason to allow it. The code does not become clearer if we do.
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.
Agree!
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.
The argument for multiple cases within parens is that curlies are normally needed for multiple sequential semicolon-separated statements, but you can have multiple cases on a single line without semicolons:
By that logic, multiple cases aren't really "statements" in the same way e.g. multiple
println
s would be, and so it makes sense that they wouldn't need curlies to allow multiple in the same expressionUh oh!
There was an error while loading. Please reload this page.
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.
Technically that's a valid argument, but cases are still multiple things that should not be grouped in parens. More reasons why:
this looks bad and we should not encourage that style.
there are subtle rules on newline insertion and indentation that depend on not being enclosed by parens. They might cause subtle problems here. I don't want to have to think about these problems or fix bugs if they come up.