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
Copy file name to clipboardExpand all lines: content/alternative-bind-variables.md
+81-9Lines changed: 81 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,6 +13,7 @@ title: SIP-NN - Bind variables within alternative patterns
13
13
| Date | Version |
14
14
|---------------|--------------------|
15
15
| Sep 17th 2023 | Initial Draft |
16
+
| Jan 16th 2024 | Amendments |
16
17
17
18
## Summary
18
19
@@ -151,7 +152,7 @@ enum Foo:
151
152
~~~
152
153
153
154
For the expression to make sense with the current semantics around pattern matches, `z` must be defined in both branches; otherwise the
154
-
case body would be nonsensical if `z` was referenced within it.
155
+
case body would be nonsensical if `z` was referenced within it (see [missing variables](#missing-variables) for a proposed alternative).
155
156
156
157
Removing the restriction would also allow recursive alternative patterns:
157
158
@@ -206,36 +207,107 @@ enum Foo[A]:
206
207
caseBaz(x) |Bar(x) =>// x: Int | A
207
208
~~~
208
209
210
+
### Given bind variables
211
+
212
+
It is possible to introduce bindings to the contextual scope within a pattern match branch.
213
+
214
+
Since most bindings will be anonymous but be referred to within the branches, we expect the _types_ present in the contextual scope for each branch to be the same rather than the _names_.
215
+
216
+
~~~scala
217
+
caseclassContext()
218
+
219
+
defrun(usingctx: Context):Unit=???
220
+
221
+
enumFoo:
222
+
caseBar(ctx: Context)
223
+
caseBaz(i: Int, ctx: Context)
224
+
225
+
deffun=thismatch
226
+
caseBar(givenContext) |Baz(_, givenContext) => run // `Context` appears in both branches
227
+
~~~
228
+
229
+
This begs the question of what to do in the case of an explicit `@` binding where the user binds a variable to the same _name_ but to different types. We can either expose a `String | Int` within the contextual scope, or simply reject the code as invalid.
230
+
231
+
~~~scala
232
+
enumFoo:
233
+
caseBar(s: String)
234
+
caseBaz(i: Int)
235
+
236
+
deffun=thismatch
237
+
caseBar(x @givenString) |Baz(x @givenInt) =>???
238
+
~~~
239
+
240
+
To be consistent with the named bindings, we argue that the code should compile and a contextual variable added to the scope with the type of `String | Int`.
241
+
242
+
### Alternatives
243
+
244
+
#### Enforcing a single type for a bound variable
245
+
246
+
We could constrain the type for each bound variable within each alternative branch to be the same type. Notably, this is what languages such as Rust, which do not have sub-typing do.
247
+
248
+
However, since untagged unions are part of Scala 3 and the fact that both are represented by the `|`, it felt more natural to discard this restriction.
249
+
250
+
#### Type ascriptions in alternative branches
251
+
252
+
Another suggestion is that an _explicit_ type ascription by a user ought to be defined for all branches. For example, in the currently proposed rules, the following code would infer the return type to be `Int | A` even though the user has written the statement `id: Int`.
253
+
254
+
~~~scala
255
+
enumFoo[A]:
256
+
caseBar[A](a: A)
257
+
caseBaz[A](a: A)
258
+
259
+
deftest=thismatch
260
+
caseBar(id: Int) |Baz(id) => id
261
+
~~~
262
+
263
+
In the author's subjective opinion, it is more natural to view the alternative arms as separate branches — which would be equivalent to the function below.
264
+
265
+
~~~scala
266
+
deftest=thismatch
267
+
caseBar(id: Int) => id
268
+
caseBaz(id) => id
269
+
~~~
270
+
271
+
On the other hand, if it is decided that each bound variable ought to be the same type, then arguably "sharing" explicit type ascriptions across branches would reduce boilerplate.
272
+
273
+
#### Missing variables
274
+
275
+
Unlike in other languages, we could assign a type, `A | Null`, to a bind variable which is not present in all of the alternative branches. Rust, for example, is constrained by the fact that the size of a variable must be known and untagged unions do not exist.
276
+
277
+
Arguably, missing a variable entirely is more likely to be an error — the absence of a requirement for `var` declarations before assigning variables in Python means that beginners can easily assign variables to the wrong variable.
278
+
279
+
It may be, that the enforcement of having to have the same bind variables within each branch ought to be left to a linter rather thana a hard restriction within the language itself.
280
+
209
281
## Specification
210
282
211
283
We do not believe there are any syntax changes since the current specification already allows the proposed syntax.
212
284
213
285
We propose that the following clauses be added to the specification:
214
286
215
-
Let $`p_1 | \ldots | p_n`$ be an alternative pattern at an arbitrary depth within a case pattern
216
-
and $`\Gamma_n`$ is the scope associated with each alternative.
287
+
Let $`p_1 | \ldots | p_n`$ be an alternative pattern at an arbitrary depth within a case pattern and $`\Gamma_n`$ is the named scope associated with each alternative.
217
288
218
-
Let the variables introduced within each alternative, $`p_n`$, be $`x_i \in \Gamma_n`$.
289
+
Let the named variables introduced within each alternative $`p_n`$, be $`x_i \in \Gamma_n`$ and the unnamed contextual variables within each alternative have the type $`T_i \in \Gamma_n`$.
219
290
220
-
Each $`p_n`$ must introduce the same set of bindings, i.e. for each $`n`$, $`\Gamma_n`$ must have the same members
221
-
$`\Gamma_{n+1}`$.
291
+
Each $`p_n`$ must introduce the same set of bindings, i.e. for each $`n`$, $`\Gamma_n`$ must have the same **named** members $`\Gamma_{n+1}`$ and the set of $`{T_0, ... T_n}`$ must be the same.
222
292
223
293
If $`X_{n,i}`$, is the type of the binding $`x_i`$ within an alternative $`p_n`$, then the consequent type, $`X_i`$, of the
224
294
variable $`x_i`$ within the pattern scope, $`\Gamma`$ is the least upper-bound of all the types $`X_{n, i}`$ associated with
225
295
the variable, $`x_i`$ within each branch.
226
296
227
297
## Compatibility
228
298
229
-
We believe the changes are backwards compatible.
299
+
We believe the changes would be backwards compatible.
230
300
231
301
# Related Work
232
302
233
303
The language feature exists in multiple languages. Of the more popular languages, Rust added the feature in [2021](https://github.com/rust-lang/reference/pull/957) and
234
304
Python within [PEP 636](https://peps.python.org/pep-0636/#or-patterns), the pattern matching PEP in 2020. Of course, Python is untyped and Rust does not have sub-typing
235
305
but the semantics proposed are similar to this proposal.
236
306
237
-
Within Scala, the [issue](https://github.com/scala/bug/issues/182) first raised in 2007. The author is also aware of attempts to fix this issue by [Lionel Parreaux](https://github.com/dotty-staging/dotty/compare/main...LPTK:dotty:vars-in-pat-alts) which
238
-
were not submitted to the main dotty repository.
307
+
Within Scala, the [issue](https://github.com/scala/bug/issues/182) first raised in 2007. The author is also aware of attempts to fix this issue by [Lionel Parreaux](https://github.com/dotty-staging/dotty/compare/main...LPTK:dotty:vars-in-pat-alts) and the associated [feature request](https://github.com/lampepfl/dotty-feature-requests/issues/12) which
308
+
was not submitted to the main dotty repository.
309
+
310
+
The associated [thread](https://contributors.scala-lang.org/t/pre-sip-bind-variables-for-alternative-patterns/6321) has some extra discussion around semantics. Historically, there have been multiple similar suggestions — in [2023](https://contributors.scala-lang.org/t/qol-sound-binding-in-pattern-alternatives/6226) by Quentin Bernet and in [2021](https://contributors.scala-lang.org/t/could-it-be-possible-to-allow-variable-binging-in-patmat-alternatives-for-scala-3-x/5235) by Alexey Shuksto.
0 commit comments