5 ms·
You mean type-classes? Scala and Haskel have them. It's certainly nice to be able to do statically type-checked stuff like: list.sorted list.max list.
by bad_user 13y ago
You mean type-classes? Scala and Haskel have them. It's certainly nice to be able to do statically type-checked stuff like:
list.sorted
list.max
list.sum
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" or "max", relying on type T implement some sort of Ordering interface, or "sum", relying on type T to be some sort of Number.
So even if you would have a way to specify that types X, Y and Z have implementations for this and that set of extension methods, it would still be of somewhat limited use, given that one wouldn't be able to also use this in code making heavy use of generics, much like how interfaces themselves aren't used much in code making use of generics, because code making use of generics is usually some dumb Container<T>, where almost nothing about T is known.
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. If the builders of C# never bothered to provide you with some common interface you want for built-in types, that's OK, because you can make one up.
Scala also has implicit conversions and "view bounds". Implicit conversions go beyond extension methods and View Bounds allow one to specify that some type X can be viewed as some type Y (either Y inherits from X, or there exists an implicit conversion from X to Y). Something like:
def sample[X <% Y](item: X) = item.callMethodFromY
But these are mostly useful for builtins and deprecated for general-purpose usage in favor of context bounds (type-classes). It's still nice that builtins such as Array[T] is viewable as a Seq[T] and Strings are viewable (in true FP tradition) as sequences of chars, allowing one to do this:
"some string".map(_.toUpper) // => "SOME STRING"
What's interesting about the above example is that mapping over a string, produces a string, highlighting another Scala feature that goes beyond type-classes:
"some string".map(_.toString)
// => Seq(s, o, m, e, " ", s, t, r, i, n, g)
This is the CanBuildFrom pattern. See this awesome example from M. Odersky himself: http://stackoverflow.com/a/1728140 http://stackoverflow.com/a/1728140
- 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.
- com2kid 13y agoOne thing I wanted to do awhile back was create an interface that could only be implemented by a class that had already implemented certain other interfaces. In retrospect, sort of a horrible idea, but I wonder, is there a type system out there expressive enough to allow that behavior? After a few moments thought I realized why it wouldn't be possible in languages that use a fixed v-table, but it should be possible in languages that use more flexible calling techniques! Or am I thinking about this too much and this is basically what type classes would buy me?
- frowaway001 13y ago> One thing I wanted to do awhile back was create an interface that could only be implemented by a class that had already implemented certain other interfaces. I think that's easily possible in Scala. I guess one wouldn't even need typeclasses for that.
- saryant 13y agoThis is essentially a Scala "self-type". trait InterfaceA trait InterfaceB { self: InterfaceA => } class A extends InterfaceA // compiles class B extends InterfaceB // does not compile class B extends InterfaceA with InterfaceB // compiles "self" isn't a keyword, you can call it whatever you like.
- com2kid 13y agoHah, now that is nice. I forget what convoluted use case I wanted it for. I ended up solving it using composition instead (which is the right solution 99% of the time anyway. :) ) Still though, I love using interfaces as contracts, so I get frequently annoyed at how poorly my tools (limited to C++2003 right now) support doing such!