Skip to content

Commit e64f108

Browse files
committed
Add more material on tuples and a summary
1 parent 66ae3f4 commit e64f108

File tree

1 file changed

+27
-7
lines changed

1 file changed

+27
-7
lines changed

docs/_docs/internals/specialized-traits.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Specialized Traits and Classes
22

33
Specialization is one of the few remaining desirable features from Scala 2 that's are as yet missing in Scala 3. We could try to port the Scala 2 scheme, which would be non-trivial since the implementation is quite complex. But that scheme is problematic enough to suggest that we also look for alternatives. A possible alternative is described here. It is meant to complement the [proposal on inline traits](https://github.com/lampepfl/dotty/issues/15532). That proposal also contains a more detailed critique of Scala 2 specialization.
4-
The parts in that proposal that mention specialization should be ignored; they are superseded by the proposal here.
4+
The parts in that proposal that mention a proposed new specialization design should be ignored; they are superseded by the proposal here.
55

66
The main problem of Scala-2 specialization is code bloat. We have to pro-actively generate up to 11 copies of functions and classes when they have a specialized type parameter, and this grows exponentially with the number of such type parameters. Miniboxing tries to reduce the number under the exponent from ~10 to 3 or 4, but it has problems dealing with arrays.
77

@@ -55,7 +55,7 @@ we require that each such anonymous class instance
5555
- cannot contain member definitions.
5656

5757
So each such class instance is of the form `new A[Ts](ps1)...(psN) {}` where
58-
`A` is a specialized trait and the type parameters `Ts` and term parameters `ps1, ,,, psN` which can also be absent.
58+
`A` is a specialized trait and the type parameters `Ts` and term parameters `ps1, ,,, psN` can also be absent.
5959

6060
The restrictions ensure that each time we create an instance of a specialized trait we know statically the classes of all `Specialized` type arguments. This enables us to implement the following expansion scheme:
6161

@@ -355,7 +355,7 @@ That would avoid the boxing at the cost of a type test in the computation of `fa
355355
def sumElems(xs: Vector[Int]): Int =
356356
val faster: faster.Vector[Int] | Null = xs match
357357
case xs: faster.Vector[_] => xs
358-
case _ => xs
358+
case _ => null
359359
var i = 0
360360
var sum = 0
361361
if faster != null then
@@ -368,12 +368,23 @@ def sumElems(xs: Vector[Int]): Int =
368368
i += 1
369369
sum
370370
```
371-
The example has shown that it is possible to have code over possibly specialized collections that is both general and high performance. But it does require a lot of hand-written boiler-plate.
371+
The example has shown that one can write code over possibly specialized collections that is both general and highly performant. But it does require a lot of hand-written boiler-plate.
372+
373+
The boilerplate could be generated automatically by an optimization phase in the compiler. Essentially, when compiling methods that take parameters whose type is a class annotated with `specializedBy`, we can do the path splitting automatically in an optimization step. The optimization would first analyze the body of the method to decide which path splitting strategy to apply.
374+
375+
We believe the three tweaks we have outlined could overcome most of the performance penalties imposed by existing unspecialized class hierarchies like collections, making their performance comparable to languages that use global monomorphization.
376+
377+
### Specializing Tuples
378+
379+
The same optimizations can also avoid boxing for tuple elements, and with it extractor-based pattern matching. Scala 3 does not currently specialize tuples at all. Scala 2 specializes pairs but not tuples of higher arity. But it uses a scheme quite different from the one proposed here.
372380

373-
The boilerplate could be generated automatically by an optimization phase in the compiler. Essentially when compiling methods that take parameters whose type is a class
374-
that's annotated with `specializedBy`, we can do the path splitting automatically in an optimization step. The optimization would first analyze the body of the method to decide which path splitting strategy to use.
381+
Scala 2 pre-generates pair classes for all combinations of primitive types and Object. Each pair class inherits or implements access methods for all primitive types and Object. This allows to
382+
arrange it so that access always goes through a specialized method that does not involve boxing. No path splitting is needed to achieve that. On the other hand, the exponentially growing amount of code that needs to be generated restricts the scheme to pairs only. Also, specialization is not done for reference types, access to fields of (say) `String` type still need a cast from `Object` to `String`.
375383

376-
I believe the three tweaks I have outlined could overcome most of the performance penalties imposed by existing unspecialized class hierarchies like collections, making their performance comparable to languages that use global monomorphization.
384+
We could adopt the Scala 2 specialization scheme for pairs. This is not hard, since no new classes need to be generated, we simply re-use the Scala 2 classes. Then the new specialization scheme would apply to tuples of higher arities. Or we forego Scala 2 specialization altogether and specialize all tuples with the new scheme.
385+
386+
The situation with functions is a bit different. Here, Scala 2 specializes functions with up to two parameters, and Scala 3 re-uses these specializations.
387+
Going beyond that requires some adaptations since functions are not implemented as classes but as lambdas that are directly supported by the JVM. So Scala 3 specialization would have to be extended to the definition of these lambdas.
377388

378389
## Going Further: Hand-written Specializations
379390

@@ -390,6 +401,15 @@ The implementation in `IntHashMap` could exploit that fact that the key type `K`
390401

391402
It would be great if we could use `IntHashMap` each time a specialized HashMap such as `HashMap$sp$Int$String` is referred to or created. In other words, `IntHashMap` should act as a drop-in replacement for `HashMap$sp$Int$String` that is selected automatically. A detailed proposal for this is left for future work.
392403

404+
## Summary
405+
406+
This proposal
407+
408+
1. _Inline traits._ With them one can create specialized modules and classes, but no specialization on type parameters is possible. Inline traits also enable new patterns for meta programming.
409+
2. _Specialized traits and classes._ With them one can create class hierarchies that can require and exploit statically known type parameters.
410+
3. _Specialized overloads and path splitting_. With these additions one can create structures that can take advantage of statically known type parameters when they are available while still working for other type parameters as well. They also allow retro-fitting specializaton to existing libraries.
411+
4. _Hand-written specializations_. They allow to make use-defined algorithmic optimizations based on statically known type parameters.
412+
393413

394414

395415

0 commit comments

Comments
 (0)