@@ -3,131 +3,154 @@ layout: doc-page
33title : " Programmatic Structural Types"
44---
55
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
113131both select members programmatically. But there are also some
114132differences.
115133
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.
119137
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.
123141
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.
130148
131- ### Reference
149+ ## Example
132150
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