Skip to content

Commit aa4217f

Browse files
committed
Add section on programmatic structural types
1 parent d1c98fd commit aa4217f

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
---
2+
layout: doc-page
3+
title: "Programmatic Structural Types"
4+
---
5+
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+
dupport structural types fully. The reflction 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.relect.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
113+
both select members programmatically. But there are also some
114+
differences.
115+
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.
119+
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.
123+
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.

0 commit comments

Comments
 (0)