Skip to content

Commit 8bd7bee

Browse files
committed
More details about structural types
1 parent 0fcbfdb commit 8bd7bee

File tree

2 files changed

+145
-122
lines changed

2 files changed

+145
-122
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ trait Dynamic { self: Typer with Applications =>
117117
*
118118
* If `U` is a value type, map `x.a` to the equivalent of:
119119
*
120-
* (x: Selectable).selectDynamic(x, "a").asInstanceOf[U]
120+
* (x: Selectable).selectDynamic("a").asInstanceOf[U]
121121
*
122122
* If `U` is a method type (T1,...,Tn)R, map `x.a` to the equivalent of:
123123
*

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

Lines changed: 144 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -3,131 +3,154 @@ layout: doc-page
33
title: "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
113131
both select members programmatically. But there are also some
114132
differences.
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

Comments
 (0)