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 implementation of `Selectable` that relies on Java reflection is
29
24
available in the standard library: `scala.reflect.Selectable`. Other
30
25
implementations can be envisioned for platforms where Java reflection
31
26
is not available.
32
27
33
-
`selectDynamic` takes a field name and returns the value associated
34
-
with that name in the `Selectable`. Similarly, `applyDynamic`
35
-
takes a method name, `ClassTag`s representing its parameters types and
36
-
the arguments to pass to the function. It will return the result of
37
-
calling this function with the given arguments.
28
+
Implementations of `Selectable` have to make available one or both of
29
+
the methods `selectDynamic` and `applyDynamic`. The methods could be members of the `Selectable` implementation or they could be extension methods.
30
+
31
+
The `selectDynamic` method takes a field name and returns the value associated with that name in the `Selectable`.
32
+
It should have a signature of the form:
33
+
```scala
34
+
defselectDynamic(name: String):T
35
+
```
36
+
Often, the return type `T` is `Any`.
37
+
38
+
The `applyDynamic` method is used for selections that are applied to arguments. It takes a method name and possibly `ClassTag`s representing its parameters types as well as the arguments to pass to the function.
39
+
Its signature should be of one of the two following forms:
Both versions are passed the actual arguments in the `args` parameter. The second version takes in addition a vararg argument of class tags that identify the method's parameter classes. Such an argument is needed
45
+
if `applyDynamic` is implemented using Java reflection, but it could be
46
+
useful in other cases as well.
38
47
39
48
Given a value `v` of type `C { Rs }`, where `C` is a class reference
40
-
and `Rs` are refinement declarations, and given `v.a` of type `U`, we
41
-
consider three distinct cases:
49
+
and `Rs` are structural refinement declarations, and given `v.a` of type `U`, we consider three distinct cases:
42
50
43
-
- If `U` is a value type, we map `v.a` to the equivalent of:
- If `U` is a method type `(T11, ..., T1n)...(TN1, ..., TNn) => R` and it is not
51
-
a dependent method type, we map `v.a(a11, ..., a1n)...(aN1, ..., aNn)` to
52
-
the equivalent of:
56
+
- If `U` is a method type `(T11, ..., T1n)...(TN1, ..., TNn): R` and it is not a dependent method type, we map `v.a(a11, ..., a1n)...(aN1, ..., aNn)` to:
where each `CT_ij` is the class tag of the type of the argument `a_ij`.
67
+
where each `CT_ij` is the class tag of the type of the formal parameter `Tij`
61
68
62
69
- If `U` is neither a value nor a method type, or a dependent method
63
70
type, an error is emitted.
64
71
65
-
We make sure that `r` conforms to type `Selectable`, potentially by
66
-
introducing an implicit conversion, and then call either
67
-
`selectDynamic` or `applyDynamic`, passing the name of the
68
-
member to access, along with the class tags of the formal parameters
69
-
and the arguments in the case of a method call. These parameters
70
-
could be used to disambiguate one of several overload variants in the
71
-
future, but overloads are not supported in structural types at the
72
-
moment.
72
+
Note that `v`'s static type does not necessarily have to conform to `Selectable`, nor does it need to have `selectDynamic` and `applyDynamic` as members. It suffices that there is an implicit
73
+
conversion that can turn `v` into a `Selectable`, and the selection methods could also be available as extension methods.
73
74
74
75
## Limitations of structural types
75
76
@@ -86,14 +87,8 @@ moment.
86
87
- In Scala 2, mutable `var`s are allowed in refinements. In Scala 3,
87
88
they are no longer allowed.
88
89
89
-
## Migration
90
-
91
-
Receivers of structural calls need to be instances of `Selectable`. A
92
-
conversion from `Any` to `Selectable` is available in the standard
93
-
library, in `scala.reflect.Selectable.reflectiveSelectable`. This is
94
-
similar to the implementation of structural types in Scala 2.
The person type adds a _refinement_ to its parent type `Record` that defines `name` and `age` fields. We say the refinement is _structural_ since `name` and `age` are not defined in the parent type. But they exist nevertheless as members of class `Person`. For instance, the following
println(s"${person.name} is ${person.age} years old.")
50
+
```
51
+
The parent type `Record` in this example is a generic class that can represent arbitrary records in its `elems` argument. This argument is a
52
+
sequence of pairs of labels of type `String` and values of type `Any`.
53
+
When we create a `Person` as a `Record` we have to assert with a typecast
54
+
that the record defines the right fields of the right types. `Record`
55
+
itself is too weakly typed so the compiler cannot know this this without
56
+
help from the user. In practice, the connection between a structural type
57
+
and its underlying generic representation would most likely be done by
58
+
a database layer, and therefore would not be a concern of the end user
59
+
60
+
`Record` extends the marker trait `scala.Selectable` and defines
61
+
a method `selectDynamic`, which maps a field name to its value.
62
+
Selecting a structural type member is done by calling this method.
63
+
The `person.name` and `person.age` selections are translated by
64
+
the Scala compiler to:
65
+
```scala
66
+
person.selectDynamic("name").asInstanceOf[String]
67
+
person.selectDynamic("age").asInstanceOf[Int]
68
+
```
69
+
70
+
Besides `selectDynamic`, a `Selectable` class sometimes also defines a method `applyDynamic`. This can then be used to translate function calls of structural members. So, if `a` is an instance of `Selectable`, a structural call like `a.f(b, c)` would translate to
println(s"${person.name} is ${person.age} years old.")
47
-
// Prints: Emma is 42 years old.
77
+
Structural types can also be accessed using Java reflection. Example:
78
+
```scala
79
+
typeCloseable= {
80
+
defclose():Unit
81
+
}
82
+
classFileInputStream {
83
+
defclose():Unit
84
+
}
85
+
classChannel {
86
+
defclose():Unit
48
87
}
49
-
}
50
88
```
89
+
Here, we define a structural type `Closeable` that defines a `close` method. There are various classes that have `close` methods, we just list `FileInputStream` and `Channel` as two examples. It would be easiest if the two classes shared a common interface that factors out the `close` method. But such factorings are often not possible if different libraries are combined in one application. Yet, we can still have methods that work on
90
+
all classes with a `close` method by using the `Closeable` type. For instance,
The call `f.close()` has to use Java reflection to identify and call the `close` method in the receiver `f`. This needs to be enabled by an import
98
+
of `reflectiveSelectable` shown above. What happens "under the hood" is then the following:
99
+
100
+
- The import makes available an implicit conversion that turns any type into a
101
+
`Selectable`. `f` is wrapped in this conversion.
102
+
103
+
- The compiler then transforms the `close` call on the wrapped `f`
104
+
to an `applyDynamic` call. The end result is:
105
+
106
+
```scala
107
+
reflectiveSelectable(f).applyDynamic("close")()
108
+
```
109
+
- The implementation of `applyDynamic` in `reflectiveSelectable`'s result
110
+
uses Java reflection to find and call a method `close` with zero parameters in the value referenced by `f` at runtime.
111
+
112
+
Structural calls like this tend to be much slower than normal method calls. The mandatory import of `reflectiveSelectable` serves as a signpost that something inefficient is going on.
113
+
114
+
**Note:** In Scala 2, Java reflection is the only mechanism available for structural types and it is automatically enabled without needing the
115
+
`reflectiveSelectable` conversion. However, to warn against inefficient
116
+
dispatch, Scala 2 requires a language import `import scala.language.reflectiveCalls`.
117
+
For a limited time, Scala 3 will honor the same language import (but not the
118
+
associated command-line option) to import `reflectiveSelectable` instead.
119
+
120
+
Before resorting to structural calls with Java reflection one should consider alternatives. For instance, sometimes a more a modular _and_ efficient architecture can be obtained using typeclasses.
0 commit comments