-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Clarify the reasons for dropping existentials #4353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Can you give an example where it gives a major gain? |
I can. I suppose that what is meant is that a type F[A] forSome { type A } can be replaced by Exists[F] where trait Exists[F[_]] {
type A
val get: F[A]
} The first problem with that is the extra boxing. The second problem is that we need one version of such Exists2[F[_,_]]
Exists3[F[_,_,_]]
ExistsH[F[_[_]]]
ExistsHA[F[_[_], _]]
ExistsFoo[F[_[_,_], _[_], _]]
// ...
// not to mention versions with bounds and variance I do find this a major loss. Now, if only we could make trait Exists[F <: AnyKind] {
type A <: AnyKind
val get: F[A]
} but that won't work, because
|
Wildcards are still supported so you can express your example as |
There's a misunderstanding I think. Wildcards won't work as well for any type mentioning the existential type more than once, such as
Sure, and I guess one could in principle try to design features to address this if needed — something like Haskell's existentials comes to mind* — but my first guess is that the problem isn't important enough (either for that or for having existentials). I'm open to evidence. I agree that documentation could say more, as always. I'm not sure what to write though, apart from the kind-monomorphic encoding you sketch. I also agree you can't abstract over it easily. For more info on the motivation, one reference (off the top of my head, with some googling) is https://groups.google.com/d/msg/scala-language/PV4q6O1qIh8/yG4p8PA2Jf8J and its discussion of existentials. Among other things:
Another email is: https://groups.google.com/d/msg/scala-internals/lCIcSDHSCyg/kkN4382tVHUJ
I'm not 100% sure of the details, but it seems relevant that in Coq, substituting Some discussion with more references is in http://guillaume.martres.me/publications/dotty-hk.pdf — basically, it explains that Scala "existentials" aren't usual existentials because they have no introduction and elimination forms (which, concretely, goes together with the fact that they're unboxed, though you could have as introduction form an unboxed (newtype-like) constructor) — and then it gives a reference to a paper by @RossTate and others.
I confess I don't know what those problems are. I know I looked at that paper years ago and it was pretty tough, and it's just for Java. |
Then maybe it just shouldn't say that the gain is minor.
Those sound like problems with the compiler implementation, moreover the old one, not necessarily the feature itself. From the rest of your comment it seems like the real reason is that all the implications of existentials as present in Scala are unknown and the easiest was to just remove them altogether. |
Actually, I should have corrected you earlier:
Not at all. You couldn't write a kind-polymorphic class WrapF {
type A
val v: F[A]
} and transforming the client code accordingly. Not that the transformation is easy to define generally, but if you wrote such code using existentials, that should be easy enough to do. I didn't say it earlier because I thought it obvious for this conversation, but I guess this is worth pointing out in the docs? I agree on the limitations of kind-polymorphism, but since even higher kinds are still waiting for formal proofs (though we're planning an attack), and even in Scalac they have IIUC two users, I'm afraid progress there will have to wait.
My impression is that those are concerns with writing down the typing rules, hence actually defining the semantics of the feature. And between doing, dunno, research or a full PhD on the topic, which might or might not work, and requiring the above encoding, the second seems better. |
I think the only disagreement there is about whether the gain from having existentials is minor.
It should not be surprising that a non-existent feature has 0 users. Like higher kinds in Java. Or generics in Go.
Let's explain as much in the docs ;)
I'm really not sure what you mean. Am I against additional research on existentials? No, I would be the first one to welcome it. The title of this issue is "Clarify the reasons for dropping existentials". |
Wondering if |
@sirinath I think the notation is the last concern, if there isn't an underlying representation in the compiler to support it. |
Action items for me:
They have a prototype in Typelevel Scala. But you're welcome to point to Haskell libraries using it — I don't really have an axe to grind, so if I'm happy to be wrong.
That's a hard debate to resolve, but maybe I can try writing something less subjective there. "We have decided to not support existentials given the open questions on the proper meaning of existentials in interaction with other features, and the alternative encodings that are available."
Well, I was explaining why I, at present, would not recommend research on the topic, based on what the literature suggests. In a sense more research is likely better than nothing, but there are only so many researchers. If somebody makes progress in the upcoming decades, people might reconsider. |
Kind-polymorphism in Typelevel Scala is as unsuitable for encoding existentials as the one in dotty, so why even mention it? Due to its limitations, of course it has next to 0 uses. I don't need to point to Haskell libraries. I can name some uses of kind-polymorphism (that I would actually implement and use) off the top of my head: kind-polymorphic versions of 👍 to your effort to make the docs less subjective and more precise! |
Tangential thought: Doesn't GADT pattern matching use a lot of the same internal machinery as existentials? |
Meanwhile, @allanrenucci asked a few questions and made me realize there might be a way to ignore boxing — whenever the existential doesn't appear at the top-level, move Assume // Scala 2:
// Defining `Foo` isn't needed
def bar = { type Foo = F[_]; ... }
// Scala 3:
def bar = { type A; type Foo = F[A] } For class fields: // Scala 2:
class Bar {
type Foo = F[_]
val v: F[_]
}
//Scala 3:
class Bar {
type A
type Foo = F[A]
val v: F[A]
} The only loophole I see are types appearing at the top-level, but I can only think of Loopholes I fear are that now some code might fail after translating because of stability restrictions — scala> type F = [X] => (X, X)
scala> class Bar {
type A
type Foo = F[A]
val v: F[A] = null
}
// defined class Bar
scala> new Bar().v
val res0: (_, _) = null // approximated type!
scala> val v = new Bar()
val v: Bar = Bar@d5ce97f
scala> v.v
val res1: F[v.A] = null But I suspect any such restrictions might be necessary for soundness. |
OK, what doesn't work if you lift //scala 2:
(1, 1): F[_] // works
//scala 3
trait Bar {
type A
val v: F[A]
}
(new Bar { type A = Int; val v = (1, 1) }: Bar) // can abstract over this:
object Bar {
def apply[X](a: F[X]): Bar = new Bar { type A = X; val v = a }
}
Bar((1, 1)) |
I think that wouldn't work to translate trait Bar {
def go: List[F[A] forSome { type A }]
} It is not equivalent to trait Bar {
type A
def go: List[F[A]]
} because in the first case the |
Also true. And I don't think CPS-transforming |
Hah, I love this quote! I'd never seen it before, but I totally get it. Sorry the work doesn't help y'all except to warn just how complicated the problem gets. I have other work on checking/inferring existential types, but unfortunately it doesn't work for impredicative existential types, which is the case y'all particularly care about. Nonetheless, there are probably some useful lessons to learn from the predicative case if y'all are interested. |
@RossTate Well "X is hard" can be as useful as "here's how to do it". Having googled, I guess your other work is https://pdfs.semanticscholar.org/89f8/13248f34a352232f18a6c0771925d0e982b4.pdf and http://cseweb.ucsd.edu/~rtate/publications/italx/existentialstr.pdf? Found those from https://scholar.google.com/citations?user=EiXpVxwAAAAJ..., and then saw the link in http://www.cs.cornell.edu/~ross/publications/italx/ . So you're saying that case is interesting even after looking at your later paper (which I didn't really study)? Linking here those papers for when we get back to the question. |
So the key insight/restriction we rely on is that predicative types have limited locations in which existentially quantified variables can go, and that a subtyping between predicative types gives a mapping between these locations. Subtyping and inference then stem from reasoning about and manipulating those mappings. Unfortunately with impredicative types, pretty much every location is existentially quantifiable. So maybe there's some way to formulate how you'll restrict existential subtyping so that more informative mappings can exist, but without that it's very hard to transfer over my work :( |
@TomasMikula You can make a newtype for existentials which works pretty well in Scala2 and doesn't box. type Type[+F[_]] <: (Any { type T })
def wrap[F[_], A](value: F[A]): Type[F] =
value.asInstanceOf[Type[F]]
def unwrap[F[_]](value: Type[F]): F[value.T] =
value.asInstanceOf[F[value.T]] |
@alexknvl OK, that addresses the boxing issue. |
Another way to read that line is "they are pretty damn hard to implement" :) I don't think we should spend too much time explaining why they are hard, so I'm going to close this issue. |
Dotty documentation lists three reasons why existentials were dropped.
Can this be illustrated on an example? What's a snippet of code that uses existentials and is problematic in this respect?
This is a little too vague of an explanation for users who are going to miss existentials.
I disagree that the gain is minor.
The text was updated successfully, but these errors were encountered: