@@ -3,131 +3,154 @@ layout: doc-page
3
3
title : " Programmatic Structural Types"
4
4
---
5
5
6
- Previously, Scala supported structural types by means of
7
- reflection. This is problematic on other platforms, because Scala's
8
- reflection is JVM-based. Consequently, Scala.js and Scala.native don't
9
- support structural types fully. The reflection based implementation is
10
- also needlessly restrictive, since it rules out other implementation
11
- schemes. This makes structural types unsuitable for e.g. modelling
12
- rows in a database, for which they would otherwise seem to be an ideal
13
- match.
14
-
15
- Dotty allows to implement structural types programmatically, using
16
- "Selectables". ` Selectable ` is a trait defined as follows:
17
-
18
- trait Selectable extends Any {
19
- def selectDynamic(name: String): Any
20
- def selectDynamicMethod(name: String, paramClasses: ClassTag[_]*): Any =
21
- new UnsupportedOperationException("selectDynamicMethod")
22
- }
23
-
24
- The most important method of a ` Selectable ` is ` selectDynamic ` : It
25
- takes a field name and returns the value associated with that name in
26
- the selectable.
27
-
28
- Assume now ` r ` is a value with structural type ` S ` . In general ` S ` is
29
- of the form ` C { Rs } ` , i.e. it consists of a class reference ` C ` and
30
- refinement declarations ` Rs ` . We call a field selection ` r.f `
31
- _ structural_ if ` f ` is a name defined by a declaration in ` Rs ` whereas
32
- ` C ` defines no member of name ` f ` . Assuming the selection has type
33
- ` T ` , it is mapped to something equivalent to the following code:
34
-
35
- (r: Selectable).selectDynamic("f").asInstanceOf[T]
36
-
37
- That is, we make sure ` r ` conforms to type ` Selectable ` , potentially
38
- by adding an implicit conversion. We then invoke the ` get ` operation
39
- of that instance, passing the the name ` "f" ` as a parameter. We
40
- finally cast the resulting value back to the statically known type
41
- ` T ` .
42
-
43
- ` Selectable ` also defines another access method called
44
- ` selectDynamicMethod ` . This operation is used to select methods
45
- instead of fields. It gets passed the class tags of the selected
46
- method's formal parameter types as additional arguments. These can
47
- then be used to disambiguate one of several overloaded variants.
48
-
49
- Package ` scala.reflect ` contains an implicit conversion which can map
50
- any value to a selectable that emulates reflection-based selection, in
51
- a way similar to what was done until now:
52
-
53
- package scala.reflect
54
-
55
- object Selectable {
56
- implicit def reflectiveSelectable(receiver: Any): scala.Selectable =
57
- receiver match {
58
- case receiver: scala.Selectable => receiver
59
- case _ => new scala.reflect.Selectable(receiver)
60
- }
61
- }
62
-
63
- When imported, ` reflectiveSelectable ` provides a way to access fields
64
- of any structural type using Java reflection. This is similar to the
65
- current implementation of structural types. The main difference is
66
- that to get reflection-based structural access one now has to add an
67
- import:
68
-
69
- import scala.reflect.Selectable.reflectiveSelectable
70
-
71
- On the other hand, the previously required language feature import of
72
- ` reflectiveCalls ` is now redundant and is therefore dropped.
73
-
74
- As you can see from its implementation above, ` reflectSelectable `
75
- checks first whether its argument is already a run-time instance of
76
- ` Selectable ` , in which case it is returned directly. This means that
77
- reflection-based accesses only take place as a last resort, if no
78
- other ` Selectable ` is defined.
79
-
80
- Other selectable instances can be defined in libraries. For instance,
81
- here is a simple class of records that support dynamic selection:
82
-
83
- case class Record(elems: (String, Any)*) extends Selectable {
84
- def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2
85
- }
86
-
87
- ` Record ` consists of a list of pairs of element names and values. Its
88
- ` selectDynamic ` operation finds the pair with given name and returns
89
- its value.
90
-
91
- For illustration, let's define a record value and cast it to a
92
- structural type ` Person ` :
93
-
94
- type Person = Record { val name: String; val age: Int }
95
- val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person]
96
-
97
- Then ` person.name ` will have static type ` String ` , and will produce ` "Emma" ` as result.
98
-
99
- The safety of this scheme relies on the correctness of the cast. If
100
- the cast lies about the structure of the record, the corresponding
101
- ` selectDynamic ` operation would fail. In practice, the cast would
102
- likely be part if a database access layer which would ensure its
103
- correctness.
104
-
105
- ### Notes:
106
-
107
- 1 . The scheme does not handle polymorphic methods in structural
108
- refinements. Such polymorphic methods are currently flagged as
109
- errors. It's not clear whether the use case is common enough to
110
- warrant the additional complexity of supporting it.
111
-
112
- 2 . There are clearly some connections with ` scala.Dynamic ` here, since
6
+ ## Syntax
7
+
8
+ ```
9
+ SimpleType ::= ... | Refinement
10
+ Refinement ::= ‘{’ RefineStatSeq ‘}’
11
+ RefineStatSeq ::= RefineStat {semi RefineStat}
12
+ RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl
13
+ ```
14
+
15
+ ## Motivation
16
+
17
+ Some usecases, such as modelling database access, are more awkward in
18
+ statically typed languages than in dynamically typed languages: With
19
+ dynamically typed languages, it's quite natural to model a row as a
20
+ record or object, and to select entries with simple dot notation (e.g.
21
+ ` row.columnName ` ).
22
+
23
+ Achieving the same experience in statically typed
24
+ language requires defining a class for every possible row arising from
25
+ database manipulation (including rows arising from joins and
26
+ projections) and setting up a scheme to map between a row and the
27
+ class representing it.
28
+
29
+ This requires a large amount of boilerplate, which leads developers to
30
+ trade the advantages of static typing for simpler schemes where colum
31
+ names are represented as strings and passed to other operators (e.g.
32
+ ` row.select("columnName") ` ). This approach forgoes the advantages of
33
+ static typing, and is still not as natural as the dynamically typed
34
+ version.
35
+
36
+ Structural types help in situations where we would like to support
37
+ simple dot notation in dynamic contexts without losing the advantages
38
+ of static typing. They allow developers to use dot notation and
39
+ configure how fields and methods should be resolved.
40
+
41
+ An [ example] ( #example ) is available at the end of this document.
42
+
43
+ ## Implementation of structural types
44
+
45
+ The standard library defines a trait ` Selectable ` in the package
46
+ ` scala ` , defined as follows:
47
+
48
+ ``` scala
49
+ trait Selectable extends Any {
50
+ def selectDynamic (name : String ): Any
51
+ def selectDynamicMethod (name : String , paramClasses : ClassTag [_]* ): Any =
52
+ new UnsupportedOperationException (" selectDynamicMethod" )
53
+ }
54
+ ```
55
+
56
+ An implementation of ` Selectable ` that relies on Java reflection is
57
+ available in the standard library: ` scala.reflect.Selectable ` . Other
58
+ implementations can be envisioned for platforms where Java reflection
59
+ is not available.
60
+
61
+ ` selectDynamic ` takes a field name and returns the value associated
62
+ with that name in the ` Selectable ` . Similarly, ` selectDynamicMethod `
63
+ takes a method name, ` ClassTag ` s representing its parameters types and
64
+ will return the function that matches this
65
+ name and parameter types.
66
+
67
+ Given a value ` v ` of type ` C { Rs } ` , where ` C ` is a class reference
68
+ and ` Rs ` are refinement declarations, and given ` v.a ` of type ` U ` , we
69
+ consider three distinct cases:
70
+
71
+ - If ` U ` is a value type, we map ` v.a ` to the equivalent of:
72
+ ``` scala
73
+ v.a
74
+ --->
75
+ (v : Selectable ).selectDynamic(" a" ).asInstanceOf [U ]
76
+ ```
77
+
78
+ - If ` U ` is a method type ` (T1, ..., Tn) => R ` with at most 7
79
+ parameters and it is not a dependent method type, we map ` v.a ` to
80
+ the equivalent of:
81
+ ``` scala
82
+ v.a
83
+ --->
84
+ (v : Selectable ).selectDynamic(" a" , CT1 , ..., CTn ).asInstanceOf [(T1 , ..., Tn ) => R ]
85
+ ```
86
+
87
+ - If ` U ` is neither a value nor a method type, or a dependent method
88
+ type, or has more than 7 parameters, an error is emitted.
89
+
90
+ We make sure that ` r ` conforms to type ` Selectable ` , potentially by
91
+ introducing an implicit conversion, and then call either
92
+ ` selectDynamic ` or ` selectMethodDynamic ` , passing the name of the
93
+ member to access and the class tags of the formal parameters, in the
94
+ case of a method call. These parameters could be used to disambiguate
95
+ one of several overload variants in the future, but overloads are not
96
+ supported in structural types at the moment.
97
+
98
+ ## Extensibility
99
+
100
+ New instances of ` Selectable ` can be defined to support means of
101
+ access other than Java reflection, which would enable usages such as
102
+ the database access example given in the "Motivation" section.
103
+
104
+ ## Limitations of structural types
105
+
106
+ - Methods with more than 7 formal parameters cannot be called via
107
+ structural call.
108
+ - Dependent methods cannot be called via structural call.
109
+ - Overloaded methods cannot be called via structural call.
110
+ - Refinement do not handle polymorphic methods.
111
+
112
+ ## Differences with Scala 2 structural types
113
+
114
+ - Scala 2 supports structural types by means of Java reflection. Unlike
115
+ Scala 3, structural calls do not rely on a mechanism such as
116
+ ` Selectable ` , and reflection cannot be avoided.
117
+ - In Scala 2, structural calls to overloaded methods are possible.
118
+ - In Scala 2, mutable ` var ` s are allowed in refinements. In Scala 3,
119
+ they are no longer allowed.
120
+
121
+ ## Migration
122
+
123
+ Receivers of structural calls need to be instances of ` Selectable ` . A
124
+ conversion from ` Any ` to ` Selectable ` is available in the standard
125
+ library, in ` scala.reflect.Selectable.reflectiveSelectable ` . This is
126
+ similar to the implementation of structural types in Scala 2.
127
+
128
+ ## Relation with ` scala.Dynamic `
129
+
130
+ There are clearly some connections with ` scala.Dynamic ` here, since
113
131
both select members programmatically. But there are also some
114
132
differences.
115
133
116
- - Fully dynamic selection is not typesafe, but structural selection
117
- is, as long as the correspondence of the structural type with the
118
- underlying value is as stated.
134
+ - Fully dynamic selection is not typesafe, but structural selection
135
+ is, as long as the correspondence of the structural type with the
136
+ underlying value is as stated.
119
137
120
- - ` Dynamic ` is just a marker trait, which gives more leeway where and
121
- how to define reflective access operations. By contrast
122
- ` Selectable ` is a trait which declares the access operations.
138
+ - ` Dynamic ` is just a marker trait, which gives more leeway where and
139
+ how to define reflective access operations. By contrast
140
+ ` Selectable ` is a trait which declares the access operations.
123
141
124
- - One access operation, ` selectDynamic ` is shared between both
125
- approaches, but the other access operations are
126
- different. ` Selectable ` defines a ` selectDynamicMethod ` , which
127
- takes class tags indicating the method's formal parameter types as
128
- additional argument. ` Dynamic ` comes with ` applyDynamic ` and
129
- ` updateDynamic ` methods, which take actual argument values.
142
+ - One access operation, ` selectDynamic ` is shared between both
143
+ approaches, but the other access operations are
144
+ different. ` Selectable ` defines a ` selectDynamicMethod ` , which
145
+ takes class tags indicating the method's formal parameter types as
146
+ additional argument. ` Dynamic ` comes with ` applyDynamic ` and
147
+ ` updateDynamic ` methods, which take actual argument values.
130
148
131
- ### Reference
149
+ ## Example
132
150
133
- For more info, see [ Issue #1886 ] ( https://github.com/lampepfl/dotty/issues/1886 ) .
151
+ <script src =" https://scastie.scala-lang.org/Duhemm/HOZFKyKLTs294XOSYPU5Fw.js " ></script >
152
+
153
+ ## Reference
154
+
155
+ For more info, see [ Rethink Structural
156
+ Types] ( https://github.com/lampepfl/dotty/issues/1886 ) .
0 commit comments