Skip to content

Commit 1d0a458

Browse files
committed
Update structural-types.md
1 parent ada55e2 commit 1d0a458

File tree

1 file changed

+54
-44
lines changed

1 file changed

+54
-44
lines changed

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

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,41 @@ configure how fields and methods should be resolved.
3535
Here's an example of a structural type `Person`:
3636

3737
```scala
38-
class Record(elems: (String, Any)*) extends Selectable:
39-
private val fields = elems.toMap
40-
def selectDynamic(name: String): Any = fields(name)
38+
type Person = Record { val name: String; val age: Int }
39+
```
40+
41+
The type `Person` adds a _refinement_ to its parent type `Record` that defines the two fields `name` and `age`. We say the refinement is _structural_ since `name` and `age` are not defined in the parent type. But they exist nevertheless as members of type `Person`.
4142

42-
type Person = Record { val name: String; val age: Int }
43-
```
43+
This allows us to check at compiletime if accesses are valid:
4444

45-
The type `Person` adds a _refinement_ to its parent type `Record` that defines the two fields `name` and `age`. 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.":
45+
```scala
46+
val person: Person = ???
47+
println(s"${person.name} is ${person.age} years old.") // works
48+
println(person.email) // error: value email is not a member of Person
49+
```
50+
How is `Record` defined, and how does `person.name` resolve ?
51+
52+
`Record` is a class that extends the marker trait [`scala.Selectable`](https://scala-lang.org/api/3.x/scala/Selectable.html) and defines
53+
a method `selectDynamic`, which maps a field name to its value.
54+
Selecting a member of a structural type is syntactic sugar for a call to this method.
55+
The selections `person.name` and `person.age` are translated by
56+
the Scala compiler to:
4757

4858
```scala
49-
val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person]
50-
println(s"${person.name} is ${person.age} years old.")
59+
person.selectDynamic("name").asInstanceOf[String]
60+
person.selectDynamic("age").asInstanceOf[Int]
61+
```
62+
63+
For example, `Record` could be defined as follows:
64+
65+
```scala
66+
class Record(elems: (String, Any)*) extends Selectable:
67+
private val fields = elems.toMap
68+
def selectDynamic(name: String): Any = fields(name)
69+
```
70+
Which allows us to create instances of `Person` like so:
71+
```scala
72+
val person = Record("name" -> "Emma", "age" -> 42).asInstanceOf[Person]
5173
```
5274

5375
The parent type `Record` in this example is a generic class that can represent arbitrary records in its `elems` argument. This argument is a
@@ -59,52 +81,45 @@ help from the user. In practice, the connection between a structural type
5981
and its underlying generic representation would most likely be done by
6082
a database layer, and therefore would not be a concern of the end user.
6183

62-
`Record` extends the marker trait [`scala.Selectable`](https://scala-lang.org/api/3.x/scala/Selectable.html) and defines
63-
a method `selectDynamic`, which maps a field name to its value.
64-
Selecting a structural type member is done by calling this method.
65-
The `person.name` and `person.age` selections are translated by
66-
the Scala compiler to:
67-
68-
```scala
69-
person.selectDynamic("name").asInstanceOf[String]
70-
person.selectDynamic("age").asInstanceOf[Int]
71-
```
72-
7384
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
7485

7586
```scala
76-
a.applyDynamic("f")(b, c)
87+
a.applyDynamic("f")(b, c)
7788
```
7889

7990
## Using Java Reflection
8091

81-
Structural types can also be accessed using [Java reflection](https://www.oracle.com/technical-resources/articles/java/javareflection.html). Example:
92+
Using `Selectable` and [Java reflection](https://www.oracle.com/technical-resources/articles/java/javareflection.html), we can select a member from unrelated classes.
93+
94+
> 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 [type classes](../contextual/type-classes.md).
95+
96+
For example, we would like to provide behavior for both [`FileInputStream`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/FileInputStream.html#%3Cinit%3E(java.io.File)) and [`Channel`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/channels/Channel.html) classes by calling their `close` method, however, these classes are unrelated, i.e. have no common supertype with a `close` method. Therefore, below we define a structural type `Closeable` that defines a `close` method.
8297

8398
```scala
84-
type Closeable = { def close(): Unit }
99+
type Closeable = { def close(): Unit }
85100

86-
class FileInputStream:
87-
def close(): Unit
101+
class FileInputStream:
102+
def close(): Unit
88103

89-
class Channel:
90-
def close(): Unit
104+
class Channel:
105+
def close(): Unit
91106
```
92107

93-
Here, we define a structural type `Closeable` that defines a `close` method. There are various classes that have `close` methods, we just list [`FileInputStream`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/FileInputStream.html#%3Cinit%3E(java.io.File)) and [`Channel`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/channels/Channel.html) 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
94-
all classes with a `close` method by using the `Closeable` type. For instance,
108+
Ideally we would add a common interface to both these classes to define the `close` method, however they are defined in libraries outside of our control. As a compromise we can use the structural type to define a single implementation for an `autoClose` method:
109+
110+
95111

96112
```scala
97-
import scala.reflect.Selectable.reflectiveSelectable
113+
import scala.reflect.Selectable.reflectiveSelectable
98114

99-
def autoClose(f: Closeable)(op: Closeable => Unit): Unit =
100-
try op(f) finally f.close()
115+
def autoClose(f: Closeable)(op: Closeable => Unit): Unit =
116+
try op(f) finally f.close()
101117
```
102118

103-
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
104-
of `reflectiveSelectable` shown above. What happens "under the hood" is then the following:
119+
The call `f.close()` requires `Closeable` to extend `Selectable` to identify and call the `close` method in the receiver `f`. A universal implicit conversion to `Selectable` is enabled by an import
120+
of `reflectiveSelectable` shown above, based on [Java reflection](https://www.oracle.com/technical-resources/articles/java/javareflection.html). What happens "under the hood" is then the following:
105121

106-
- The import makes available an implicit conversion that turns any type into a
107-
`Selectable`. `f` is wrapped in this conversion.
122+
- The implicit conversion wraps `f` in an instance of `scala.reflect.Selectable` (which is a subtype of `Selectable`).
108123

109124
- The compiler then transforms the `close` call on the wrapped `f`
110125
to an `applyDynamic` call. The end result is:
@@ -113,16 +128,14 @@ of `reflectiveSelectable` shown above. What happens "under the hood" is then the
113128
reflectiveSelectable(f).applyDynamic("close")()
114129
```
115130
- The implementation of `applyDynamic` in `reflectiveSelectable`'s result
116-
uses Java reflection to find and call a method `close` with zero parameters in the value referenced by `f` at runtime.
131+
uses [Java reflection](https://www.oracle.com/technical-resources/articles/java/javareflection.html) to find and call a method `close` with zero parameters in the value referenced by `f` at runtime.
117132

118133
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.
119134

120135
**Note:** In Scala 2, Java reflection is the only mechanism available for structural types and it is automatically enabled without needing the
121136
`reflectiveSelectable` conversion. However, to warn against inefficient
122137
dispatch, Scala 2 requires a language import `import scala.language.reflectiveCalls`.
123138

124-
Before resorting to structural calls with Java reflection one should consider alternatives. For instance, sometimes a more modular _and_ efficient architecture can be obtained using type classes.
125-
126139
## Extensibility
127140

128141
New instances of `Selectable` can be defined to support means of
@@ -179,13 +192,10 @@ differences.
179192
is, as long as the correspondence of the structural type with the
180193
underlying value is as stated.
181194

182-
- [`Dynamic`](https://scala-lang.org/api/3.x/scala/Dynamic.html) is just a marker trait, which gives more leeway where and
183-
how to define reflective access operations. By contrast
184-
`Selectable` is a trait which declares the access operations.
185-
186195
- Two access operations, `selectDynamic` and `applyDynamic` are shared
187196
between both approaches. In `Selectable`, `applyDynamic` also may also take
188197
[`java.lang.Class`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Class.html) arguments indicating the method's formal parameter types.
189-
[`Dynamic`](https://scala-lang.org/api/3.x/scala/Dynamic.html) comes with `updateDynamic`.
198+
199+
- `updateDynamic` is unique to [`Dynamic`](https://scala-lang.org/api/3.x/scala/Dynamic.html) but as mentionned before, this fact is subject to change, and shouldn't be used as an assumption.
190200

191201
[More details](structural-types-spec.md)

0 commit comments

Comments
 (0)