Skip to content

Rethink Typeclass Derivation #6153

Closed
Closed
@odersky

Description

@odersky

I have opened this issue to collect ideas and requirements how we want to evolve the typeclass derivation framework in https://dotty.epfl.ch/docs/reference/contextual/derivation.html. The goal is to come up with something lightweight that can be used as a basis for @milessabin's (and possibly other's) designs for high-level typeclass derivation, and that can also stand on its own as a low-level derivation API.

At the SIP retreat, Miles presented his current design. It included a set of low-level "erased" abstractions that are implemented by compiler-generated code. These abstractions are quite similar in scope to what's supported by Generic and Mirror in the current implementation. But there are also differences. (I am writing this down as I recounted it from the SIP retreat, please correct where I am inaccurate).

Differences

  1. The erased API is typeless, having Any for all inputs and outputs. By contrast, the current API does expose types to some degree even though it uses casts internally. This difference has probably to do with the fact that the erased API was intended for internal use only.

  2. The erased API does not cover labels. Labels are treated only at the type level.

  3. Erased API implementations are meant to be generated automatically for all case classes, case objects, and sealed classes and traits (and we'd have to extend that to all enum values as well). By contrast the current implementation generates a Generic instance only if there is a derives clause. This is not a hard restriction since Generic instances can be generated also after the fact if they are summoned as an implied instance. But that second way of doing it can lead to code duplication.

  4. The erased API distinguishes between sums and products. In an ADT each case gets its own variant of Generic and the Generic for the overall sum type exposes a way to navigate to the Generic of the correct case. By contrast the currently implemented API exposes a single Generic for the whole ADT.

Discussion

Here are some comments on each of these four differences.

  1. it's probably uncontentious that a published API should expose fine-grained types where possible, so that user's programs don't have to do asInstanceOf everywhere.

  2. Labels should be treated only at the type level. At run-time, we can rely already on getClass.getName and productElementNames, so no new functionality is needed.

  3. It would be great if we could generate derivation infrastructure unconditionally for all sealed sum types (including enums), all case classes and objects and all enum values. The constraint to make this feasible is size of the generated code. The additional code we generate for a case class should be modest. In particular, it would be good if no additional class was generated. A case class already generates two JVM classes, one for the class itself and the other for its companion object. It would be problematic to unconditionally generate more classes that serve type class derivation. Also, the additional overhead to support an enum value should be close to zero. The current implementation does not fulfil this requirement since each generated Generic is its own class, so generating them unconditionally for both a sum type and all its cases would lead to code bloat and code duplication.

  4. If we want to generate derivation infrastructure unconditionally, we are forced to have separate infrastructure for sums and products to avoid code duplication.

Goal

So, the ideal API would be something like Miles' erased API, but with usable types and without needing to create extra classes in cases and enum values. The challenge is to come up with something along these lines.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions