4 ms·
The issue is not the variance of a specific data structure, but of how variance itself is expressed, which differs from one language to another. What you descri
by pron 8y ago
The issue is not the variance of a specific data structure, but of how variance itself is expressed, which differs from one language to another. What you described is one variance policy.
- int_19h 8y agoCan you give an example of a language variance policy that is 1) sound, and 2) cannot be mapped onto CLR variance declarations? I'll grant you that it's possible to come up with policies that are not sound (e.g. covariant mutable collections, like both Java and .NET do with built-in arrays) that cannot be so mapped. But how many languages have run into this as an issue in practice? By far the most common policy, in any case, is "no variance", which maps just fine. And you don't need to support variance to consume .NET interfaces that do have variance declarations, for the reasons I have explained in my earlier comment - so it doesn't really affect interop.
- pron 8y agoSure. Java uses use-site variance. Clojure has an "all ways" variance, as it's untyped. But Java, Kotlin (mostly declaration-site, some use-site) and Clojure all share the same data types without any need for runtime conversions. In general, you want to bake as little of the type-system as possible into the runtime; just as much as necessary to provide your most essential features. Reifying generic types with subtypable indices is a feature with very little benefit and a pretty big cost in lost flexibility.
- int_19h 8y agoYou can do use-site declarations for upper boundaries in C# as well with "where". There's no equivalent to "super", though. The benefit of having this on VM level is the same as having classes on VM level - type safety rules are consistent and uniform, and you can't write a verified (in IL terms) component in one language that can be suddenly made unsafe by another language. Also, I don't see how you'd implement variance for is/as (instanceof and casts in Java) without runtime support; JVM languages don't have this issue only because there's no way to query an object for implementation of a particular instantiation of a generic interface on JVM in general, due to type erasure. But in C#, you can e.g. do: var xs = new List<Derived>(); var ys = xs as IEnumerable<Base>(); and it'll work, even if these two lines are in totally different components. How would you do this in a typesafe manner (i.e. "as" only works if Derived is actually inherited from Base) on JVM?
- pron 8y ago> How would you do this in a typesafe manner (i.e. "as" only works if Derived is actually inherited from Base) on JVM? On the JVM or in Java? In Java you don't do it in such a safe manner. Whether you should or shouldn't be able to is another matter. If you want to write a JVM language where you want to do that safely (like Ceylon), you just reify your type -- at the cost of interop and reuse. Opting into reification is cheaper (at runtime) than opting out.
- int_19h 8y agoLet me rephrase that: how do you do it across languages? And why shouldn't you be able do it? Instantiations of generic interfaces are distinct types - the compile-time type system says so, and of course their members have different types too, so it can't possibly be a single type. So why can't you implement two different interface types on one class, just because they happen to be instantiations of the same generic? No matter how you slice it, it's an implementation restriction leaking out in a way that is inconsistent with the type system.
- pron 8y ago> how do you do it across languages? You don't. Haskell doesn't do it for its C calls. > And why shouldn't you be able do it? You are able to do it, but it comes at the cost of restricting your languages to the design of the runtime. It ends up being a question of which you prefer. Different languages that can freely share code and data, or languages that are either very restricted or require a lot of (runtime) effort to share code and data. > so it can't possibly be a single type List<String> and List<Integer> may be two different types in Java, but they're the same type in Clojure. Erasing generic types helps you enjoy both the type safety of Java and the flexibility of Clojure, at the tiny cost of some unsafe-ish reflective operations, out of many that are unsafeish anyway. > No matter how you slice it, it's an implementation restriction leaking out in a way that is inconsistent with the type system. It's not a restriction because a JVM language could choose to reify its generics, at the cost of foregoing efficient interop. It just so happens that most JVM languages choose not to do it that way.