-
Notifications
You must be signed in to change notification settings - Fork 29
SIP-69: Existential containers #101
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
base: main
Are you sure you want to change the base?
Conversation
content/existential-containers.md
Outdated
Defining `Polygon` as a type class rather than an abstract class to be inherited allows us to retroactively state that squares are polygons without modifying the definition of `Square`. | ||
Sticking to subtyping would require the definition of an inneficient and verbose wrapper class. | ||
|
||
Alas, type classes offer limited support for type erasure–the eliding of some type information at compile-time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean elided at runtime, I think? maybe I misunderstand, but also, I don't know why type erasure is relevant at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did mean compile-time but the sentence can be phrased differently. The point is that you can't easily write a list of things that have the same type class. Meanwhile, it is easy to write a list of things that have the same upper bound.
content/existential-containers.md
Outdated
xs.maxByOption((a) => a.witness.area(a.value)) | ||
``` | ||
|
||
The type `AnyPolygon` conceptually represents a type-erased polygon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I don't understand why we're talking about type erasure here. Maybe it makes sense somehow, but it's not explained sufficiently for me to follow it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize I may simply have a different definition of "type erasure". I will rephrase.
|
||
One problem not addressed by the proposed encoding is the support of multiple type classes to form the interface of a specific container. | ||
For example, one may desire to create a container of values whose types conform to both `Polygon` _and_ `Show`. | ||
We have explored possible encodings for such a feature but decided to remove them from this proposal, as support for multiple type classes can most likely be achieved without any additional language change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intent to answer this more fully before the proposal comes to a vote? Or do we expect it to remain an open question until some future iteration after the SIP lands?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would propose that as a future proposal, if necessary. That said it is likely that the SIP committee would not have to formally weigh in this addition because it would only relate to the way existential containers are encoded in the library, without any other change to the language.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
support for multiple type classes can most likely be achieved without any additional language change.
you suggest that you can synthesize a single witness: Polygon & Show
that can resolve extension methods from either without a language change?
I guess the construction is the harder part - resolution would "just work"
For example, one may desire to create a container of values whose types conform to both `Polygon` _and_ `Show`. | ||
We have explored possible encodings for such a feature but decided to remove them from this proposal, as support for multiple type classes can most likely be achieved without any additional language change. | ||
|
||
Another open question relates to possible language support for shortening the expression of a container type and/or value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto on whether this might get filled in soon, or it's definitely a "someday" thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a someday thing but, again, these open questions can hopefully be answered without introducing more changes to the language.
Co-authored-by: Seth Tisue <seth@tisue.net>
Co-authored-by: Seth Tisue <seth@tisue.net>
Co-authored-by: Seth Tisue <seth@tisue.net>
Co-authored-by: Seth Tisue <seth@tisue.net>
The call to `largest` is illegal because, although there exist witnesses of the `Polygon` and `Hexagon`'s conformance to `Polygon`, no such witness exists for their least common supertype. | ||
In other words, it is impossible to call `largest` with an heterogeneous sequence of polygons. | ||
|
||
## Proposed solution |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section is under-specified. Reading it, I have no idea what the proposed solution is, even though I generally agree on the value of the feature being proposed
We need several more use case examples, which are less contrived than Square
Rectangle
and Polygon
. Preferably from real open-source libraries
Someone should be able to read the proposed solution have zero idea how it is implemented, and still get the general idea of what the proposed language feature is about. Right now, that is not the case, so this section of the proposal is incomplete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can write other examples but I disagree that the one in the document is contrived. It also generalizes very easily to any situation involving a set of classes conforming to the same type class. Any instance of a situation where the "upper bound" of a set of types is defined retroactively would look like the proposed example.
def lookup(key: List[Containing[Hashable]]) = ???
def zipAll[E](xss: List[Containing[Iterator]{ type Element = E }]) = ???
Instead of justifying existential containers, however, I propose to clarify that the SIP is about sugaring the selection of a method rather than proposing the containers themselves, as those can be defined in library space.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You really need to justify both the language changes and library support. The language change is useless without the library support, but most people would not be familiar with this library-level technique.
List[Containing[Hashable]]
is definitely a lot more concrete than Seq[Containing[Polygon]]
. We need more examples like that.
-
I've found use for the equivalent of
Seq[Containing[upickle.default.Writer]]
myself, which contains a list of things that can be converted to JSON -
Scalatags currently uses methods taking
(args: Frag*)
with implicit conversions from various types toFrag
. it could conceivable instead use(args: Containing[Fragable]*)
with aFragable
typeclass to render the value to HTML. Is that better or worse? Why?
|
||
|
||
A more formal exploration of the state of the art as been documented in a research paper presented prior to this SIP [2]. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This proposal is missing a section on Alternatives
. I can think of a few myself:
-
What if we just add a
Containing[T]
type to the standard library; people could then use it without needing a language/compiler change at all? Could it even be a third-party library? -
What about implicit conversions and implicit constructors? Those are widely used today, often referred to as the "magnet pattern"
Why is the proposed solution better than the alternatives I listed here? What other alternatives should we be aware of, if any?
content/existential-containers.md
Outdated
|
||
### Specification | ||
|
||
Existential containers are encoded as follows: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear to me which part of this specification is user-land library code, and which section is compiler/language-level changes. Perhaps we can split it explicitly into two sections?
|
||
Another open question relates to possible language support for shortening the expression of a container type and/or value. | ||
|
||
## Related work |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from Swift, what other languages have a feature like this? Does Haskell have something similar? Or Rust? Both of those languages have typeclass-like features and presumably would encounter the same issues around e.g. hetoregenous collections. How do they solve it, or do they not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposal mentions dynamic traits in Rust. Those work almost exactly like Swift but are slightly less expressive. The proposal also cite a paper documenting other related work from scientific literature.
I'll add a note mentioning Haskell's ExistentialQuantification
extension.
Comment from the peanut gallery: I agree with Haoyi that the structure of the proposal is currently a little confusing. Specifically, since it jumps directly to implementation details, it's very unobvious what the userland code would look like. I'd recommend putting a motivating example of "this is what the end result would look like" before getting into the details of the encoding of That said, I love the meat of this proposal -- it's reifying exactly the pattern that I've had to build by hand a number of times for various systems (and which I've been occasionally teaching to my teams), so it would be a real win IMO if we had an official and concise solution. |
Apply @lihaoyi's suggestions
type Self | ||
|
||
/** A value together with an evidence of its type conforming to some type class. */ | ||
sealed trait Containing[Concept <: TypeClass]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does it only work with this upper bound or can it be { type Self <: AnyKind }
- so you are not forced to extend this TypeClass trait?
|
||
## Appendix | ||
|
||
The following is a possible implementation of existential containers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a version that doesn't rely on the experimental modularity language import (and doesn't require the typeclass to extend TypeClass
):
/** A value together with an evidence of its type conforming to some type class. */
sealed trait Containing[TC[_]]:
/** The type of the contained value. */
type Value: TC as witness
/** The contained value. */
val value: Value
object Containing:
/** Wraps a value of type `V` and a corresponding typeclass instance `TC[V]` into a `Containing[TC]`. */
def apply[TC[_]](v: Any)[V >: v.type](using TC[V]): Containing[TC] =
new Containing[TC]:
type Value >: V <: V
val value: Value = v
|
||
### Specification | ||
|
||
Assuming the existence of an abstraction named `Containing[TC]` for representing containers pairing an arbitrary value with a witness of its conformance to some type class `TC` in the standard library, the compiler injects the selection of the `value` field implicitly when a method of `Containing[TC]` is selected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is in the Specification
section, can I assume that "the compiler injects ..." is in fact part of what this SIP specifies? If so, it should be fleshed out:
- How does this interact with other adaptations, including implicit conversions and apply insertion?
- In which condition does this adaptation triggers exactly? Is Containing treated specially by the compiler or can we (and should we) generalize this?
Otherwise, this should be in a separate section (example or appendix)
stage: design | ||
status: submitted | ||
presip-thread: n/a | ||
title: SIP-NN - Existential Containers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The SIP number is already known
### Other concerns | ||
|
||
This document has been written under the experimental modularity improvements for Scala 3. | ||
Although the proposed feature is fully expressible without those changes, the encoding of existential containers can only work with the "old" (i.e., the one currently used in production) or "new" type class style. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to read this sentence a few times to get the point so maybe it would be good to rephrase it somehow (it's not clear at first which expression only
refers to)
No description provided.