"Opaque" inheritence with explicit bounds #25137
SimonGuilloud
started this conversation in
Feature Requests
Replies: 1 comment
-
|
similar situation: the companion object of a case class extends |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Dear everyone,
I would have use of a pattern allowing "opaque" traits that expose only control sub/supertype information.
Concretely, you may want to inherit from some class, but not expose that you do, or only expose something weaker. This is useful if you inherit in a complex or very implementation-specific way from a class but would only like to expose inheritance from a higher sealed trait. Let's take a concrete example for motivation
Let's take an example for motivation.
Let say you have in a given library fixed some ADT describing a syntax tree
In some other file, you want to add the square operation. Note that the
sealedis capital to thee development of a library of utilities (for example an evaluator), as it ensures complete matching.You can't directly extend
Expr, but you still should be able to make it fully interoperable with the existing library:That's very good! Now in your Square-aware code you can have special support for it, but it is still fully compatible with the preexisting library. The interpreter for example will still work exactly as planned.
This is great, but it exposes Square as a subtype of *, whereas what you want to show ideally is only
Sum <: Expr. In this simple case it's not much of a problem, but it is for more complex structure. For example, you may want to define a class ofSum:But what should it extend?
+? But what if the list contains only 1 or 0 elements? If it contains 0 elements, it should be a particular instance ofCst(0). If it contains one expressionethen it should match that very expression. So it should extend something different based on the particular content of the list¹. To implement that, there are essentially two possibilities: Self types and opaque traits.For self types, it looks like this:
This essentially work, and from outside you never see the implementation detail of
Sum. Any piece of code not Sum-aware will work as usual, but you can also enable special support for sums by typetesting expressions overSum. You can also do an unapply method forSum.However, it has a significant issue:
Sum <: Exprdoesn't hold. So one has to always carry ecplicitely the type of things asSum & Exprwhich is confusing and cumbersome.Alternatively, we can do a similar construct with an opaque type:
Now we have the proper subtyping relation, but typetesting over Sum becomes impossible, as it is an abstract type (even though in reality it is a union of concrete types, similar to the sealed trait). You can create an
unapplymethod, but it's still a bit ugly.I don't know if this example is compelling for you the reader, but this is a pattern that I'm facing over and over in a project of mine, and it works well if not for those limitations. In particular for the user this is much nicer than having a separate ADT with conversion functions or something like that. Additionally, when those ADTs are much more complicated (e.g. they are GADTs instead), exposing the (too precise) implementation inheritance tends to confuse and trigger bugs in overload resolution and especially in type class resolutions, and probably also has a cost on performances.
Shadowingthe more precise concrete type and showing only<: Exprhelps a lot, and is also much more helpful to the user to understand a possible error from their part.All that to say, I would love it if it was possible to have traits extends sealed traits (I don't see how it could be unsound, given that the two very similar patterns above work. In particular the interpretation of the trait as being equal to the union of its instantiation should be a model and hence a proof that it is consistent), or to have "opaque trait/class", e.g.
Thoughts? Any particular reasons (abstract) traits cannot be declared to extend other sealed traits?
¹ (In this specific case you could always add a 0 to the list to guarantee 2 elements, but let's suppose we can't)
Beta Was this translation helpful? Give feedback.
All reactions