Skip to content

Choose Primitives for Typelevel and Meta Programming in Scala 3.0 #5086

Closed
@odersky

Description

@odersky

Over the summer, we investigated a range of techniques to do typelevel and meta programming in
Scala.

These are valuable experiments, but we still have to settle on a set of techniques to support in Scala 3.0. As always, we want a minimal set of constructs that covers the use cases we envision.

Use Cases

We classify use cases according to their priority.

High priority

These should definitely be supported:

  1. As a minimal requirement, we want to do generic tuples of arbitrary size. These can used as building blocks for many other data-generic operations.
  2. In particular, we want to support use cases arising in query languages (e.g., projections, joins). These should scale to large schemas with hundreds of columns.
  3. We also want to provide good support in the area of typeclass derivation (which will probably need some other ingredients as well, which are not yet covered by the techniques discussed here).

Medium priority

These are "good to haves" if they are natural fits for the techniques we consider:

  1. Sized data
  2. Typed string interpolators

Low priority

These are probably out of scope for Scala 3.0, but might become interesting at some point in the future:

  1. Refinement types
  2. Theorem proving
  3. Type providers

Concerns

To evaluate a combination of techniques, we should consider the following concerns.

  1. Expressive and legible types,
  2. Suitability for ensuring increased safety through type precision,
  3. Separation of interfaces and implementatons, with support for separate compilation,
  4. Avoid duplications between type level and term level,
  5. Short compile times,
  6. Efficient generated code,
  7. Keep it simple and maintainable.

Note that some of these concerns conflict each other (e.g. 3/4).

Classification

A first classification of techniques is whether they support data generic programming through generic code, or/and whether they support specialization of code to specific types. Specialization is important for efficiency. The covered techniques support these as follows:

  • data genericity: supported by match types, typeof, rewrite methods
  • specialization: supported by rewrite methods, quote & splice

Evaluation

Quotes & splices are a given since they support blackbox macros and code introspection. My current tendency is to also take match types as a given. They are the only technique that ensures
separation between interface and implementation and they have better compile-time performance than the other techniques.

Match types can be combined with rewrite methods or typeof to achieve data genericity, but one might also use them with normal code, if we can come up with good typing rules that link match expressions with match types. So I believe further evaluation is needed whether match types should be complemented by some other typelevel technique, or whether they are good enough alone.

That leaves specialization. The minimalist standpoint here would be to use quote&splice for that, since we have it already. On the other hand, rewrite matches provide a lot of expressive power and convenience for specializing code (e.g. see how generic tuples can be specialized in #4964, file
library/src-scala3/scala/Tuple.scala). If we wanted to support that through quote&splice we'd need a rich semantic API that exposed the finer points of pattern matching. Or, alternatively, we keep rewrite matches in inlined code.

A Strawman Proposal

  1. Keep and evolve match types
  2. Implement typing rules so that a match expression can be checked to have a given expected match type.
  3. Revert from rewrite methods to the preceding scheme for inlining.
  4. Put inline after pickling, i.e. at the same time as ReifyQuotes which handles quotes and splices.
  5. Keep the idea of rewrite matches (probably call them inline matches instead), which are executed at compile time in inlined code.
  6. Keep some form of implicit matches but execute them at inlining time. That way, implicit matches can still compute implementations and prioritize between alternative implicits, but they cannot compute new types.

It's quite a bit of work, so we should discuss whether that's what we want before embarking on an implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    itype:metaIssues about process/similar

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions