4 ms·
> You mean type-classes? Yep. > Scala and Haskel have them. Correction: Haskell has them, Rust has a limited form of them, and Scala encodes them using a hor
by catnaroek 13y ago
> You mean type-classes?
Yep.
> Scala and Haskel have them.
Correction: Haskell has them, Rust has a limited form of them, and Scala encodes them using a horrible hack.
> The problems with languages like C# (or Java) go beyond the lack of type-classes or similar. For example, given a type such as List<T>, you're restricted to define methods only "for all T", as these languages don't allow you to define methods only "for some T". Examples include methods such as "sorted", relying on type T to be part of the Ordering type-class, or "max", relying on type T to be part of the Number type-class.
Both C# and Java have bounded generics. The problem is that bounded quantification is fundamentally tied to class/interface hierarchies.
> The best part about type classes is that you don't have to control/modify the type's implementation, in order to make it implement a certain type-class. Type-classes are like open interfaces.
I know.
> Scala also has implicit conversions and "view bounds".
To be honest, I am not fond of those. (And, in general, I am not fond of the way Scala does things.) If I want to view an X as a Y, a regular function "f : X -> Y" can already do that. The notion of subtyping roughly corresponds to the idea that certain functions of the form "upcast : Subtype -> Supertype" are worth implicitly applying. I do not agree with this idea.
- bad_user 13y ago> Haskell has them, and Scala encodes them using a horrible hack You can view it like that, my perspective is that in Scala the underlying mechanism (passing the dictionary around by means of implicit parameters) is more explicit. Much like how prototypes in Javascript work, versus class-based OOP. With prototypes you can have class-based design, but you can also go beyond that. And this is similar to how Scala deals with type-classes. This does have a cost in terms of learning curve, but then you've got extra flexibility. CanBuildFrom is not a type-class. On implicit conversions and view bounds, I'm not fond of them either. I actually hope that they'll go away completely. I think they made it in the language (and survive), only because Scala runs on the JVM and they had to make the builtins (like Arrays, Strings) behave nicely without much overhead. In Haskell, Strings really are lists of chars. In Scala you can't do it, unless they introduced their own String class, which would have been awful, or if they introduced some sort of implicit conversion mechanism, which they did. And this could have been some magic that Scala's compiler applies for builtins, but personally I dislike compiler magic (that you can't hook into) more than I dislike hacks made for interoperability (if only they got rid of that implicit conversion from Any to String in Predef, which is so freaking annoying).
- catnaroek 13y ago> You can view it like that, my perspective is that in Scala the underlying mechanism (passing the dictionary around by means of implicit parameters) is more explicit. What you lose is compiler-enforced coherence. When you use type classes, certain functions come with the expectation that you will consistently pass the same dictionary around between calls. With actual type classes, this is a given: there can be at most one instance of a type class for a type (or tuple of types). With Scala's implicits, you can trick the compiler into passing different dictionaries between two calls that expect the same dictionary. I am aware that this is a tradeoff: Haskell's type classes are antimodular (because a global set of instances must be kept in order to guarantee instance uniqueness per type) and Scala's implicits can be used in potentially incoherent ways. (The only way to escape this tradeoff would be to use dependent types to explicitly establish a connection between values and the dictionaries that must be passed around.) Personally, I find antimodularity annoying, but incorrectness freaking scary. So I prefer Haskell's type classes. > In Haskell, Strings really are lists of chars. In Scala you can't do it, unless they introduced their own String class, which would have been awful, or if they introduced some sort of implicit conversion mechanism, which they did. I do not find linked lists of Chars to be all that useful. In fact, most of the time they just get in the way. Fortunately, all I have to do is turn on the -XOverloadedStrings GHC extension.
- bad_user 13y agoI only scratched the surface of Haskel, so I don't really know what's annoying and what isn't :-) I think Arrays in Scala are more important, because Scala introduced its own collections library and you end up manipulating Arrays a lot, since they are so pervasive and it's rather nice for Arrays to be viewed as Seqs. On the other hand I would have been happy with just extension methods and given that simple extension methods made their way in Scala 2.10, I hope they'll break backwards compatibility at some point and pull implicit conversions out of the language - which isn't possible without a source-code migration tool, ala Go, but I saw that people on Scala's mailing list dream of one, so there is hope :-)
- catnaroek 13y ago
- bad_user 13y ago> Both C# and Java have bounded generics. The problem is that bounded quantification is fundamentally tied to class/interface hierarchies. Bounded generics do not solve the problem I tried to express. Here's a Scala sample: case class Container[T](elems: T*) { def max(implicit ev: T <:< Comparable[T]) = elems.reduceLeft((a, b) => { if (a.compareTo(b) > 1) a else b }) } Comparable is not a type-class, it's the standard java.lang.Comparable interface. Yet the defined method, "max", only works if the container's generic T implements Comparable[T]. As in ... scala> Container("1", "3", "2").max res: String = 3 Of course, Java's standard interfaces are normally useless, so Integers don't implement Comparable ... scala> Container(1, 3, 2).max <console>:10: error: Cannot prove that Int <:< Comparable[Int]. Container(1, 3, 2).max ^ Note that this restriction is only on max() and not on the generic class-wide definition of T. In Java and C#, you can only do this: class Container<T extends Comparable<T>> /* ... */ Oh, but look, now the whole container can only be used for things that are Comparable and nothing else.
- catnaroek 13y agoIn both C# and Java, methods themselves can be generic: class ListExtras { static <T extends Comparable<T>> T max (List<T> ts) { T res = ts.get(0); for (T t : ts) if (res.compareTo(t) < 0) res = t; return res; } // ... } C#/Java-style bounded quantification is enough for this particular use case. My complaint is that bounded quantification in these languages is tied to inheritance as shown by <T extends Comparable<T>>. In Haskell and Rust, bounded quantification is tied to type classes, which, as you said, are open - a type class instance for a given type may be made anywhere in the program, not just where the type is defined. So bounded quantification in Haskell and Rust (and its emulation in Scala via implicits) is strictly more powerful than bounded quantification in C# and Java. As an aside, C# is a little less retarded than Java, so even primitive types like int are objects, and, in particular, int implements IComparable<int>.