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
An intuitive, readable, and extendible way to deconstruct case classes in pattern matching.
17
+
An readable, extendible, and intuitive way to deconstruct case classes in pattern matching.
18
18
19
-
// TODO: many times requested, find more references than the contributors thread
20
-
21
-
// TODO: use lingo of spec
19
+
Link to work in progress implementation lives here: https://github.com/Jentsch/dotty
22
20
23
21
## Motivating Examples
24
22
@@ -33,7 +31,7 @@ val annasCity = user match
33
31
caseUser(name ="Anna", city = c) => c
34
32
```
35
33
36
-
The Deconstruction allows the same syntax as the construction and seems to be what people intuitive expect. See //TODO: Examples
34
+
The Deconstruction allows the same syntax as the construction and seems to be what people intuitively expect. See for example the very first post in [Scala Contributors Thread][contributors-thread] for this topic.
37
35
38
36
Without names in patterns user have to use underscore a lot. The example above would be written as, and is the same what the compiler generates:
39
37
@@ -44,13 +42,13 @@ val annasCity = user match
44
42
caseUser("Anna", _, c) => c
45
43
```
46
44
47
-
This makes it hard which parameter means what, basically the same idea as for named arguments. (IDEs help here, by showing the names.)
45
+
This makes it hard which parameter means what, basically the same idea as for named arguments. Adding underscores until the compile is happy is also not a great experience. IDEs can help here, by showing the names.
48
46
49
-
In addition, it breaks every time a field of `User` gets added, rearranged, or removed.
50
-
In the worst case it breaks silently, if two fields with the type switch places.
47
+
In addition, the code breaks every time a field of `User` gets added, rearranged, or removed.
48
+
In the worst case it breaks silently, if two fields with the same type switch places.
51
49
52
-
Personal motivation comes from using to offend https://www.scalatest.org/user_guide/using_matchers#matchingAPattern
53
-
and got bitten every time the data model changed slightly.
50
+
My personal motivation comes from using [`matchPattern` in ScalaTest](https://www.scalatest.org/user_guide/using_matchers#matchingAPattern)
51
+
and got bitten by it every time my data model changed slightly.
54
52
55
53
## Counter-Examples
56
54
@@ -60,31 +58,14 @@ This SIP doesn't aim to allow pattern where parameters go into the pattern, e.g:
60
58
valmap=Map("Berlin"->10, "Paris"->5)
61
59
62
60
map match
63
-
caseMap("Paris"-> five) => five
61
+
caseMap("Paris"= five) => five
64
62
```
65
63
66
-
//TODO: find references where this feature was requested
67
-
68
64
## Design
69
65
70
66
Goal is similarity between construction and deconstruction of case classes.
71
67
72
-
Before this was invalid syntax, so this shouldn't affect any existing Scala program.
73
-
74
-
### Desugaring
75
-
76
-
> One important principle of Scala’s pattern matching design is that case classes should be abstractable. I.e. we want to be able to represent the abilities of a case class without exposing the case class itself. That also allows code to evolve from a case class to a regular class or a different case class while maintaining the interface of the old case class. [Martin Odersky](https://contributors.scala-lang.org/t/pattern-matching-with-named-fields/1829/52)
77
-
78
-
To create a user defined destructor user the `@names` annotation.
Up to now this was invalid syntax, so this shouldn't affect any existing Scala program.
88
69
89
70
### Mixed usage
90
71
@@ -93,30 +74,70 @@ But they have no motivational use case. Maybe they should be disallowed.
93
74
94
75
```scala
95
76
caseUser("Anna", city = c) =>// Mixed usage seems wired
96
-
caseUser(_, city = c) =>// Leading underscore are espacially useless
77
+
caseUser(_, city = c) =>// Leading underscore are especially useless
97
78
```
98
79
99
80
### Disallow same name twice
100
81
101
-
// TODO: Motivate this restriction
102
-
103
82
```scala
104
83
caseUser(city = city1, city = city2) => a // error city is used twice
105
84
caseUser(name1, name = name2) => a // error city is used twice
106
85
```
107
86
87
+
The same should happen if the `User` had a field with a [deprecated name](https://www.scala-lang.org/api/current/scala/deprecatedName.html).
88
+
108
89
### Order of name and term
109
90
110
-
Normally an equal sign assigns the result of the right side to the left side. With the proposed syntax that's not the case. However, for most, if not all, people that's the correct. The order seems the requires a look ahead in the parser.
91
+
Normally an equal sign assigns the result of the right side to the left side. With the proposed syntax that's not the case. However, for most, if not all, people that's the correctorder.
111
92
112
-
// TODO: Performance test
93
+
This order seems the requires a single look ahead in the parser.
113
94
114
-
## Implementation
95
+
### Desugaring
96
+
97
+
> One important principle of Scala’s pattern matching design is that case classes should be abstractable. I.e. we want to be able to represent the abilities of a case class without exposing the case class itself. That also allows code to evolve from a case class to a regular class or a different case class while maintaining the interface of the old case class. [Martin Odersky](https://contributors.scala-lang.org/t/pattern-matching-with-named-fields/1829/52)
115
98
99
+
* Allow the same feature of case classes:
100
+
* Provide arbitrary names
101
+
* Enable [`@deprecatedName`](https://www.scala-lang.org/api/current/scala/deprecatedName.html), which leads to multiple names for a single field. Using both should be an error.
102
+
* Maybe provide additional named fields. For example for our `User`:
116
103
117
-
'Simple' rewrite of patterns. If a pattern with a name is encountered, the compiler looks up the index of those names and places the tree accordingly.
104
+
```scala
105
+
caseUser(ageInDays =3650) => ...
106
+
```
107
+
108
+
But this could be an awful feature.
109
+
* Maybe leave fields without names
110
+
111
+
This SIP proposes basically to use [shapeless records](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#extensible-records) for the specific purpose of pattern matching.
112
+
For incomplete list of alternatives see [alternative desugaring](#alternative-desugaring) below.
113
+
114
+
Pro:
115
+
116
+
* with shapeless records we have a good understanding what we are doing, however we need to tweak the encoding a bit
117
+
* with some helper definitions the usage would be quite short
118
+
* reuse possible
119
+
120
+
Con:
121
+
122
+
* opens an can of worms, as this encoding could be useful for other requested features like [named tuples](named-tuple), which would require more thought-out design
123
+
* no support for `@deprecatedName`
124
+
* identifiers are represented with string literals
125
+
126
+
Example for user:
127
+
128
+
```scala
129
+
objectUser:
130
+
defunapply(user: User):Option[
131
+
(String, Int, String) &
132
+
{ typeNames= ("name", "age", "city") }
133
+
] = (user.name, user.age, user.city).asInstanceOf
134
+
```
135
+
136
+
The reason to pull the names out, instead of keeping them near to their type, is to prevent extractors to accidentally leaking the name.
118
137
119
-
// TODO: Brag about the simplicity of the implementation
138
+
## Implementation
139
+
140
+
'Simple' rewrite of patterns. If a pattern with a name is encountered, the compiler looks up the index of those names and places the tree accordingly.
120
141
121
142
Example:
122
143
@@ -134,7 +155,6 @@ case User(
134
155
135
156
## Drawbacks
136
157
137
-
138
158
### Allow skipping arguments
139
159
140
160
Whenever a single named argument is used in pattern, the pattern can have fewer arguments than the unapply.
@@ -144,42 +164,90 @@ This leads to inconsistency, as pointed out by Lionel Parreaux in the [Scala Con
144
164
caseUser(age = _) =>"Just wanted to use the extractor, lol!"
145
165
```
146
166
147
-
148
167
## Alternatives
149
168
150
169
### Without any changes to the language
151
170
152
171
One alternative way of archiving most objectives, that is doable with current Scala, is to use specialized extractors.
153
172
154
173
```scala
155
-
caseclassUser(age: Int)
156
-
157
174
objectUser:
158
175
objectage:
159
176
defunapply(user: User):Option[Int] =
160
177
Some(user.age)
161
178
162
-
User(10)match
179
+
usermatch
163
180
caseUser.age(y) => y
164
181
```
165
182
166
-
Libraries like [Monocle][monocle] could be extended to reduce the boilerplate, but still some boilerplate would remain.
183
+
Libraries like [Monocle][monocle] could be extended to reduce the boilerplate, but some boilerplate would remain.
167
184
In addition, this breaks the intuitive similarity between construction and deconstruction.
168
185
169
-
### Type as vehicle
186
+
### Alternative desugaring
187
+
188
+
#### Use underscore methods
189
+
190
+
In the sprit of https://dotty.epfl.ch/docs/reference/changed-features/pattern-matching.html#name-based-match:
191
+
192
+
```scala
193
+
objectUser:
194
+
classUserMatching(user: User):
195
+
def_1= user.name
196
+
...
197
+
198
+
def_name= user.name
199
+
inlinedef_age= _2
200
+
```
201
+
202
+
Pro:
203
+
204
+
* allows to add more fields
205
+
* allows `@deprecatedName`
206
+
207
+
Con:
208
+
209
+
* How to detect that a name means the same as a position? Maybe detect simple patterns like the last line in the example?
* the type and the method can inherited from traits and allow some kind of reuse
242
+
243
+
Con:
244
+
245
+
* the type and the method can be defined in unrelated traits. Only at the use site of the can be checked if the type and the method agree on the arity of the pattern.
246
+
* no clear way of encoding deprecated name
247
+
180
248
### Named Tuple Arguments / Anonymous Case Classes
181
249
182
-
This was mentioned in the discussion about [Named Tuple Arguments / Anonymous Case Classes][named-tuple] as bonus, that named tuples could transport the names from unapply to the pattern.
250
+
This was mentioned in the discussion about [Named Tuple Arguments / Anonymous Case Classes][named-tuple] as bonus, that named tuples could transport the names from unapply to the pattern.
183
251
This would be more generic and could handle user defined extractors.
184
252
185
253
### Partial destructuring in guards
@@ -191,7 +259,11 @@ Lionel Parreaux proposed a more powerful mechanism, where if guards of cases cou
191
259
caseUser(age =Age(years)) => years // both cases do the same thing
192
260
```
193
261
194
-
His proposal is striclty more powerfull, but arguably less intuitive. Both, Pattern matching with named fields and Partial destructuring in guards could be implemented along each other. Named fields for simple patterns and destructuring in guards for complex patterns. However, they offer two ways to do the same thing and could lead to lots of bike shedding.
262
+
His proposal is strictly more powerful, but arguably less intuitive. Both, Pattern matching with named fields and Partial destructuring in guards could be implemented along each other. Named fields for simple patterns and destructuring in guards for complex patterns. However, they offer two ways to do the same thing and could lead to lots of bike shedding, if both got added to the language.
263
+
264
+
## Open questions
265
+
266
+
It could be useful to add extractors with just named fields to sealed traits and enums.
195
267
196
268
## References
197
269
@@ -202,6 +274,6 @@ His proposal is striclty more powerfull, but arguably less intuitive. Both, Patt
202
274
*[Partial Destructuring in Guards][partial-destructuring-in-guards]
0 commit comments