Hi Scala Community!
This thread is the SIP Committee’s request for comments on a proposal to change how Structural Types work in the language. You can find all the details here.
Summary
Scala already supports structural types, which look like the following:
type Foo = {
val x: Int
def m(a: Int, b: Int): Int
}
and can be used as follows:
class Bar {
val x: Int = 5
def m(a: Int, b: Int): Int = a + b
}
val foo: Foo = new Bar()
println(foo.x)
println(foo.m(4, 5))
Behind the scenes, the implementation of the actual field accesses and method calls are hard-coded in the compiler, using platform-dependent techniques (reflection on the JVM, special linker features on Native and JS).
This proposal extends the above structural types mechanism so that the implementation can be programmatically defined in user-space.
The standard library defines a trait Selectable
in the package scala
, defined as follows:
trait Selectable extends Any {
def selectDynamic(name: String): Any
def applyDynamic(name: String, paramClasses: ClassTag[_]*)(args: Any*): Any =
new UnsupportedOperationException("applyDynamic")
}
An implementation of Selectable
that relies on Java reflection is available in the standard library: scala.reflect.Selectable
, and will be special-cased by the compilers for JS and Native to support existing uses of structural types from Scala 2.
selectDynamic
takes a field name and returns the value associated with that name in the Selectable
. Similarly, applyDynamic
takes a method name, ClassTag
s representing its parameters types and the arguments to pass to the function. It will return the result of calling this function with the given arguments.
Given a value v
of type C { Rs }
, where C
is a class reference and Rs
are refinement declarations, and given v.a
of type U
, we consider three distinct cases:
- If
U
is a value type (i.e., it’s aval
or adef
without()
), we mapv.a
to the equivalent of:
v.a
--->
(v: Selectable).selectDynamic("a").asInstanceOf[U]
- If
U
is a method type(T11, ..., T1n)...(TN1, ..., TNn) => R
and it is not a dependent method type, we mapv.a(a11, ..., a1n)...(aN1, aNn)
to the equivalent of:
v.a(arg1, ..., argn)
--->
(v: Selectable).applyDynamic("a", CT11, ..., CTn, ..., CTN1, ... CTNn)
(a11, ..., a1n, ..., aN1, ..., aNn)
.asInstanceOf[R]
- If
U
is neither a value nor a method type, or a dependent method type, an error is emitted.
We make sure that v
conforms to type Selectable
with (v: Selectable), potentially introducing an implicit conversion, and then call either
selectDynamicor
applyDynamic`, passing the name of the member to access, along with the class tags of the formal parameters and the arguments in the case of a method call. These parameters could be used to disambiguate one of several overload variants in the future, but overloads are not supported in structural types at the moment.
Limitations
-
var
fields are not supported. - Dependent methods cannot be called via structural call.
- Overloaded methods cannot be called via structural call.
- Refinements do not handle polymorphic methods.
Implications
Overall, this proposal is almost a strict extension of the existing cases of structural types, so few code should be impacted. However, as it stands, this proposal drops support for var
fields and overloaded methods, compared to Scala 2.
Opening this Proposal for discussion by the community to get a wider perspective and use cases.