7 ms·
It died, because .net supports reified types, which make interoperability pretty much impossible. Contrary to popular opinion, erasure is a superior solution t
by laureny 13y ago
It died, because .net supports reified types, which make interoperability pretty much impossible.
Contrary to popular opinion, erasure is a superior solution to reification for many reasons, and this is one of them.
- tomp 13y agoCan you be more specific? I know that reification can bloat runtime memory requirements, but I'd like to know about other disadvantages.
- noblethrasher 13y agoThis might help: http://beust.com/weblog/2011/07/29/erasure-vs-reification/ http://beust.com/weblog/2011/07/29/erasure-vs-reification/
- tomp 13y agoNot really... this only mentions that reified generics would be incompatible with current Java collections, which is obviously a problem that Java has, not that reified generics have (C# does great!).
- bad_user 13y agoC# was designed for the CLR and the CLR was designed for C#. F# on the other hand has 2 generics systems in the same language, one inherited from C# and one for ML-style Hindley–Milner based on type-erasure and they don't interact well. Java's generics can't be built on top of the CLR's reified generics, but this goes for other languages as well - Haskell, ML, Scala, you name it. Basically, reified generics work well in case you design the language in combination with the VM, otherwise it becomes a huge PITA. FYI, the CLR's reified generics is one reason for why the JVM is a better target for other languages. For dynamic languages, they are a problem because the bytecode generator has to work around them. For static languages, such as Scala or anything in the ML family, it's a problem because either (a) they are designed for co/contra-variance or (b) they don't support higher-kinded types or (c) they don't support rank-2 types or (d) in case you do need co/contra-variance rules, for some reason Microsoft chose to restrict the declarations only to generic interfaces and delegates, so you can't design a List<+T> class for example (to be honest, I'm not sure if this last one is a limitation of the language or the CLR). Also, having reified generics is less important than you think. Scala for example can do specialization for primitive types and because the type-system is much stronger, coupled with an awesome collections library and pattern matching, the flow of the types is much better. For example, I can't remember any instance in which I felt the need to do a (obj isInstanceOf List<int>) check, immutable collections can be covariant without issues and for everything else there are type-classes, something which C# sorely lacks. IMHO, in spite of people's opinion on the matter, Java's type-erased generics is one of its best features.
- Locke1689 13y agoAlso, having reified generics is less important than you think. Scala for example can do specialization for primitive types and because the type-system is much stronger, coupled with an awesome collections library and pattern matching, the flow of the types is much better. For example, I can't remember any instance in which I felt the need to do a (obj isInstanceOf List<int>) check, immutable collections can be covariant without issues and for everything else there are type-classes, something which C# sorely lacks. I was under the impression that Scala just used Manifests and type tokens (a la my colleague Neal Gafter's Super Type Tokens) to get around the problem of erasure. I don't see any evidence that this actually works better, though.
- bad_user 13y agoYes, it also has TypeTags, allowing you to inspect exactly what the compiler saw at compile-time in detail. That's not really what one uses in practice, e.g... scala> val list = List(null, 1, 2, "any") list: List[Any] = List(null, 1, 2, any) scala> list.collect { case x: Int => x } res0: List[Int] = List(1, 2) scala> Vector(null, "hello", 3).collectFirst { case i: Int => i * 2 } res1: Option[Int] = Some(6) scala> import collection.immutable.BitSet scala> BitSet(1,2,3).map(_ + 1) res2: immutable.BitSet = BitSet(2, 3, 4) scala> BitSet(1,2,3).map(_.toString) res3: immutable.SortedSet[String] = TreeSet(1, 2, 3) To be honest, tags are indeed needed for creating arrays, since they are reified by the JVM: scala> import scala.reflect.ClassTag scala> def myAwesomeArray[T : ClassTag] = new Array[T](10) scala> myAwesomeArray[Int] res4: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0) One common problem is that overriding of parameters doesn't work for containers due to type erasure, so you can't have overrides on type parameters, because the JVM gets in the way (i.e. the following does not work): def sum(list: List[String]) = ??? def sum(list: List[Int]) = ??? However, Scala has something more powerful (type-classes): trait Monoid[T] { def plus(x: T, y: T): T def zero: T } implicit object IntMonoid extends Monoid[Int] { def plus(x: Int, y: Int) = x + y val zero = 0 } implicit object StringMonoid extends Monoid[String] { def plus(x: String, y: String) = x + y val zero = "" } def sum[T : Monoid](list: List[T]) = { val monoid = implicitly[Monoid[T]] list.foldLeft(monoid.zero)(monoid.plus) } scala> sum(List("a", "b", "c", "d")) res5: String = abcd scala> sum(List(1,2,3,4)) res6: Int = 10 But wait, we can go much further with implicits that actually dictate the return type: trait SetBuilder[T, R] { def empty: R def buildInstance(param: T): R def concat(s1: R, s2: R): R } implicit object IntSetBuilder extends SetBuilder[Int, BitSet] { def empty = BitSet.empty def buildInstance(param: Int) = BitSet(param) def concat(s1: BitSet, s2: BitSet) = s1 ++ s2 } implicit object StringSetBuilder extends SetBuilder[String, Set[String]] { def empty = Set.empty[String] def buildInstance(param: String) = Set(param) def concat(s1: Set[String], s2: Set[String]) = s1 ++ s2 } def buildSetFrom[T,R](params: T*)(implicit builder: SetBuilder[T,R]): R = { if (params.nonEmpty) builder.concat( builder.buildInstance(params.head), buildSetFrom(params.tail : _*) ) else builder.empty } scala> buildSetFrom(1,2,3) res7: scala.collection.immutable.BitSet = BitSet(1, 2, 3) scala> buildSetFrom("a", "b", "c") res8: Set[String] = Set(a, b, c) Scala in general allows for code that is much more generic than C#, without having the need for reified generics.
- kvb 13y agoI'd love to see some evidence for these claims. Personally, I think that .NET's type system is quite elegant compared to the JVM's, and that .NET's combination of value types and reified generics gives several concrete advantages (better memory usage, avoids many Java pitfalls such as "why can't I use a T[]?", etc.).
- frowaway001 13y ago> .NET's type system is quite elegant What do you think about being able to use Void in Generics? Java's issue of not supporting primitive types pales in comparison to that. Also, while reified generics and structs are fine, most of the optimizations one would reasonably suspect to be supported are just not there. Some time ago, it was still faster to pass value types by reference ... that's just embarrassing. Maybe RyuJIT does better here?
- kvb 13y agoThere are lots of things that could be better, and I don't mean to imply that .NET's system is perfect. But the fact that List<byte> contains an array of bytes rather than an array of much larger objects is often important in practice, and dwarfs any complaints I might have. Not being able to use System.Void in .NET generics is an occasionally frustrating quirk, not a huge problem, in my experience. Indeed, it's quite easy to create a new type with no public constructors to achieve the equivalent result (and F# does this with the unit type).