Skip to content

Commit 61afaa0

Browse files
committed
Update docs
1 parent b1bf69c commit 61afaa0

File tree

4 files changed

+143
-58
lines changed

4 files changed

+143
-58
lines changed

compiler/src/dotty/tools/dotc/typer/Dynamic.scala

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,18 +131,24 @@ trait Dynamic {
131131
* and `x.a` is of type `U`, map `x.a` to the equivalent of:
132132
*
133133
* ```scala
134-
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
134+
* x1.selectDynamic("a").asInstanceOf[U]
135135
* ```
136+
* where `x1` is `x` adapted to `Selectable`.
136137
*
137138
* Given `x.a(a11, ..., a1n)...(aN1, ..., aNn)`, where `x.a` is of (widened) type
138-
* `(T11, ..., T1n)...(TN1, ..., TNn) => R`, it is desugared to:
139+
* `(T11, ..., T1n)...(TN1, ..., TNn): R`, it is desugared to:
139140
*
140141
* ```scala
141-
* (x:selectable).applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn)
142-
* (a11, ..., a1n, ..., aN1, ..., aNn)
143-
* .asInstanceOf[R]
142+
* x1.applyDynamic("a", (a11, ..., a1n, ..., aN1, ..., aNn)
143+
* .asInstanceOf[R]
144+
* ```
145+
* If this call resolves to an `applyDynamic` method that takes a `ClassTag[?]*` as second
146+
* parameter, we further rewrite this call to
147+
* scala```
148+
* x1.applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn)
149+
* (a11, ..., a1n, ..., aN1, ..., aNn)
150+
* .asInstanceOf[R]
144151
* ```
145-
*
146152
* where CT11, ..., CTNn are the class tags representing the erasure of T11, ..., TNn.
147153
*
148154
* It's an error if U is neither a value nor a method type, or a dependent method

docs/docs/reference/changed-features/structural-types-spec.md

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -14,62 +14,63 @@ RefineStat ::= ‘val’ VarDcl | ‘def’ DefDcl | ‘type’ {nl} TypeDcl
1414

1515
## Implementation of structural types
1616

17-
The standard library defines a trait `Selectable` in the package
18-
`scala`, defined as follows:
17+
The standard library defines a universal marker trait `Selectable` in the package `scala`:
1918

2019
```scala
21-
trait Selectable extends Any {
22-
def selectDynamic(name: String): Any
23-
def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any =
24-
new UnsupportedOperationException("applyDynamic")
25-
}
20+
trait Selectable extends Any
2621
```
2722

2823
An implementation of `Selectable` that relies on Java reflection is
2924
available in the standard library: `scala.reflect.Selectable`. Other
3025
implementations can be envisioned for platforms where Java reflection
3126
is not available.
3227

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+
def selectDynamic(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:
40+
```scala
41+
def applyDynamic(name: String)(args: Any*): T
42+
def applyDynamic(name: String, ctags: ClassTag[?]*)(args: Any*): T
43+
```
44+
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.
3847

3948
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:
4250

43-
- If `U` is a value type, we map `v.a` to the equivalent of:
51+
- If `U` is a value type, we map `v.a` to:
4452
```scala
45-
v.a
46-
--->
47-
(v: Selectable).selectDynamic("a").asInstanceOf[U]
53+
v.selectDynamic("a").asInstanceOf[U]
4854
```
4955

50-
- 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:
57+
```scala
58+
v.applyDynamic("a")(a11, ..., a1n, ..., aN1, ..., aNn)
59+
.asInstanceOf[R]
60+
```
61+
If this call resolves to an `applyDynamic` method of the second form that takes a `ClassTag[?]*` argument, we further rewrite this call to
5362
```scala
54-
v.a(arg1, ..., argn)
55-
--->
56-
(v: Selectable).applyDynamic("a", CT11, ..., CTn, ..., CTN1, ... CTNn)
57-
(a11, ..., a1n, ..., aN1, ..., aNn)
58-
.asInstanceOf[R]
63+
v.applyDynamic("a", CT11, ..., CT1n, ..., CTN1, ... CTNn)(
64+
a11, ..., a1n, ..., aN1, ..., aNn)
65+
.asInstanceOf[R]
5966
```
60-
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`
6168

6269
- If `U` is neither a value nor a method type, or a dependent method
6370
type, an error is emitted.
6471

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.
7374

7475
## Limitations of structural types
7576

@@ -86,14 +87,8 @@ moment.
8687
- In Scala 2, mutable `var`s are allowed in refinements. In Scala 3,
8788
they are no longer allowed.
8889

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.
9590

96-
## Reference
91+
## Context
9792

9893
For more info, see [Rethink Structural
9994
Types](https://github.com/lampepfl/dotty/issues/1886).

docs/docs/reference/changed-features/structural-types.md

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ layout: doc-page
33
title: "Programmatic Structural Types"
44
---
55

6+
## Motivation
7+
68
Some usecases, such as modelling database access, are more awkward in
79
statically typed languages than in dynamically typed languages: With
810
dynamically typed languages, it's quite natural to model a row as a
@@ -29,25 +31,93 @@ configure how fields and methods should be resolved.
2931

3032
## Example
3133

34+
Here's an example of a structural type `Person`:
3235
```scala
33-
object StructuralTypeExample {
34-
35-
case class Record(elems: (String, Any)*) extends Selectable {
36-
def selectDynamic(name: String): Any = elems.find(_._1 == name).get._2
36+
class Record(elems: (String, Any)*) extends Selectable {
37+
private val fields = elems.toMap
38+
def selectDynamic(name: String): Any = fields(name)
3739
}
38-
3940
type Person = Record {
4041
val name: String
4142
val age: Int
4243
}
44+
```
45+
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
46+
program would print "Emma is 42 years old.":
47+
```scala
48+
val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person]
49+
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
71+
```scala
72+
a.applyDynamic("f")(b, c)
73+
```
74+
75+
## Using Java Reflection
4376

44-
def main(args: Array[String]): Unit = {
45-
val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person]
46-
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+
type Closeable = {
80+
def close(): Unit
81+
}
82+
class FileInputStream {
83+
def close(): Unit
84+
}
85+
class Channel {
86+
def close(): Unit
4887
}
49-
}
5088
```
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,
91+
```scala
92+
import scala.reflect.Selectable.reflectiveSelectable
93+
94+
def autoClose(f: Closeable)(op: Closeable => Unit): Unit =
95+
try op(f) finally f.close()
96+
```
97+
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.
51121

52122
## Extensibility
53123

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
type Closeable = {
2+
def close(): Unit
3+
}
4+
5+
class FileInputStream:
6+
def close(): Unit = ()
7+
8+
class Channel:
9+
def close(): Unit = ()
10+
11+
import scala.reflect.Selectable.reflectiveSelectable
12+
13+
def autoClose(f: Closeable)(op: Closeable => Unit): Unit =
14+
try op(f) finally f.close()

0 commit comments

Comments
 (0)