You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| July 21st 2022 | Expanded Other Conerns Section |
17
17
## Summary
18
18
19
19
The current state of Scala 3 makes braces optional around blocks and template definitions (i.e. bodies of classes, objects, traits, enums, or givens). This SIP proposes to allow optional braces also for function arguments.
@@ -24,10 +24,13 @@ The changes have been implemented and and made available under the language impo
24
24
## Motivation
25
25
26
26
After extensive experience with the current indentation rules I conclude that they are overall a big success.
27
-
However. they still feel incomplete and a bit unsystematic since we can replace `{...}` in the majority of situations, but there are also important classes of situations where braces remain mandatory. In particular, braces are currently needed around blocks as function arguments.
27
+
However, they still feel incomplete and a bit unsystematic since we can replace `{...}` in the majority of situations, but there are also important classes of situations where braces remain mandatory. In particular, braces are currently needed around blocks as function arguments.
28
28
29
-
It seems very natural to generalize the current class syntax indentation syntax to function arguments. In both cases, an indentation block is started by a colon at the end of a line.
29
+
It seems very natural to generalize the current class syntax indentation syntax to function arguments. In both cases, an indentation block is started by a colon at the end of a line. Doing so will bring two major benefits:
30
30
31
+
- Better _consistency_, since we avoid the situation where braces are sometimes optional and in other places mandatory.
32
+
- Better _readability_ in many common use cases, similar to why current
The proposed solution changes the meaning of the following code fragments:
108
111
```scala
@@ -123,13 +126,134 @@ If there would be code using these idioms, it can be rewritten quite simply to a
123
126
=>Int)
124
127
```
125
128
126
-
### Other concerns
129
+
## Other concerns
130
+
131
+
### Tooling
127
132
128
133
Since this affects parsing, the scalameta parser and any other parser used in an IDE will also need to be updated. The necessary changes to the Scala 3 parser were made here: https://github.com/lampepfl/dotty/pull/15273/commits. The commit that embodies the core change set is here: https://github.com/lampepfl/dotty/pull/15273/commits/421bdd660b0456c2ff1ae386f032c41bb1e0212a.
129
134
130
-
### Open questions
135
+
### Handling Edge Cases
136
+
137
+
The design intentionally does not allow `:` to be placed after an infix operator or after a previous indented argument. This is a consequence of the following clause in the spec above:
138
+
139
+
> To this purpose we introduce a new `<colon>` token that reads as
140
+
the standard colon "`:`" but is generated instead of it where `<colon>`
141
+
is legal according to the context free syntax, but only if the previous token
142
+
is an alphanumeric identifier, a backticked identifier, or one of the tokens `this`, `super`, "`)`", and "`]`".
143
+
144
+
This was done to prevent hard-to-decypher symbol salad as in the following cases: (1)
145
+
```scala
146
+
a < b ||:// illegal
147
+
valx= f(c)
148
+
x >0
149
+
```
150
+
or (2)
151
+
```scala
152
+
source -->: x =>// illegal
153
+
valy= x * x
154
+
println(y)
155
+
```
156
+
or (3)
157
+
```scala
158
+
xo.fold:
159
+
defaultValue
160
+
:// illegal
161
+
x => f(x)
162
+
```
163
+
or (4)
164
+
```scala
165
+
xs.groupMapReduce: item =>
166
+
key(item)
167
+
: item =>// illegal
168
+
value(item)
169
+
: (value1, value2) =>// illegal
170
+
reduce(value1, value2)
171
+
```
172
+
173
+
I argue that the language already provides mechanisms to express these examples without having to resort to braces. (Aside: I don't think that resorting to braces occasionally is a bad thing, but some people argue that it is, so it's good to have alternatives). Basically, we have three options
174
+
175
+
- Use parentheses. That always works if the argument is a lambda.
176
+
- Use an explicit `apply` method call.
177
+
- Use a `locally` call.
178
+
179
+
Here is how the examples above can be rewritten so that they are legal but still don't use braces:
180
+
181
+
For (1), use `locally`:
182
+
```scala
183
+
a < b ||locally:
184
+
valx= f(c)
185
+
x >0
186
+
```
187
+
For (2), use parentheses:
188
+
```scala
189
+
source --> ( x =>
190
+
valy= x * x
191
+
println(y)
192
+
)
193
+
```
194
+
For (3) and (4), use `apply`:
195
+
```scala
196
+
xo.fold:
197
+
defaultValue
198
+
.apply:
199
+
x => f(x)
200
+
201
+
xs.groupMapReduce: item =>
202
+
key(item)
203
+
.apply: item =>
204
+
value(item)
205
+
.apply: (value1, value2) =>
206
+
reduce(value1, value2)
207
+
```
208
+
**Note 1:** I don't argue that this syntax is _more readable_ than braces, just that it is reasonable. The goal of this SIP is to have the nicer syntax for
209
+
all common cases and to not be atrocious for edge cases. I think this
210
+
goal is achieved by the presented design.
211
+
212
+
**Note 2:** The Scala compiler should add a peephole optimization
213
+
that elides an eta expansion in front of `.apply`. E.g. the `fold` example should be compiled to the same code as `xs.fold(defaultValue)(x => f(x))`.
214
+
215
+
**Note 3:** To avoid a runtime footprint, the `locally` method should be an inline method. We can achieve that by shadowing the stdlib, or else we can decide on a different name. `nested` or `block` have been proposed. In any case this could be done in a separate step.
216
+
217
+
### Syntactic confusion with type ascription
218
+
219
+
A frequently raised concern against using `:` as an indentation marker is that it is too close to type ascription and therefore might be confusing.
220
+
221
+
However, I have seen no evidence so far that this is true in practice. Of course, one can make up examples that look ambiguous. But as outlined, the community build and the dotty code base do not contain a single case where
222
+
a type ascription is now accidentally interpreted as an argument.
223
+
224
+
Also the fact that Python chose `:` for type ascription even though it was already used as an indentation marker should give us confidence.
225
+
226
+
In well written future Scala code we can use visual keys that would tell us immediately which is which. Namely:
227
+
228
+
> If the `:` or `=>` is at the end of a line, it's an argument, otherwise it's a type ascription.
229
+
230
+
According to the current syntax, if you want a multi-line type ascription, you _cannot_ write
231
+
```scala
232
+
anExpr:
233
+
aType
234
+
```
235
+
It _must be_ rewritten to
236
+
```scala
237
+
anExpr
238
+
: aType
239
+
```
240
+
(But, as stated above, it seems nobody actually writes code like that).
241
+
Similarly, it is _recommended_ that you put `=>` in a multi-line function type at the start of lines instead of at the end. I.e. this code does not follow the guidelines (even though it is technically legal):
242
+
```scala
243
+
xs.map: ((x: Int) =>
244
+
Int)
245
+
```
246
+
You should reformat it like this:
247
+
```scala
248
+
valy= xs.map: ((x: Int)
249
+
=>Int)
250
+
```
251
+
If we propose these guidelines then the only problem that remains is that if one intentionally writes confusing layout _and_ one reads only superficially then things can be confusing. But that's really nothing out of the ordinary.
252
+
253
+
254
+
## Open questions
131
255
132
-
None for me.
256
+
None directly related to the SIP. As mentioned above we should decide eventually whether we should stick with `locally` or replace it by something else. But that is unrelated to the SIP proper and can be decided independently.
0 commit comments