4 ms·
No, not that. I agree with you on structural typing. What I mean is what the Scala doc calls "context bounds": http://docs.scala-lang.org/tutorials/FAQ/context
by norswap 11y ago
No, not that. I agree with you on structural typing.
What I mean is what the Scala doc calls "context bounds": http://docs.scala-lang.org/tutorials/FAQ/context-and-view-bounds.html http://docs.scala-lang.org/tutorials/FAQ/context-and-view-bo... (bit outdated: view bounds are deprecated)
You could think of it as "abstracting over the adapter pattern", i.e. being able to write methods that take as parameter things that can be adapted to an interface.
(And of course the main point of the Adapter pattern is retroactive implementation of interfaces.)
But, and this is important, you get to pass the actual object instead of the adapter. This matters when you need to required multiple adapters, or when you work with object equality. The object type can also flow through the type parameters (although that's sort of doable with adapters only).
Finally, because a typeclass instance is actually a single object per type satisfying an interface, you can use it for things like factory methods (what they call constructor classes in Haskell).
It's close to what I propose here: https://discuss.kotlinlang.org/t/kotlin-and-the-expression-problem/1416 https://discuss.kotlinlang.org/t/kotlin-and-the-expression-p...
but there are differences. Scala typeclasses are based on implicits, this gives you more control to select the adapter you want depending on where the code runs. But, unlike what I propose, it's dependent on static typing, so it's not possible to select the adapter depending on the run-time type.
- mike_hearn 11y agoRight, I remember your thread, which is I guess why I'm a bit confused, as you said what you really wanted to do is make a type implement a new interface. I do understand Haskell type classes but in a language like that, they're almost obligatory as it's not an OO language at all. But in something like Kotlin, you should need it far more rarely, so the automatic/implicit "conversion" (I realise it's not quite "conversion" in Haskell) is less vital. So some convenience for creation of adapters would seem to get you the same practical benefits in much the same way. With respect to the difference between using a singleton vs a freshly allocated wrapper - is it really worth complicating the Java interop for what seems like an optimisation? Code that works with object identity is rare in all the code I've written and can't even be done at all in a pure functional language. And requiring multiple 'adapters', you mean interfaces I think (?) can be done already by imposing multiple generic type constraints (the where clause). The VM should be able to optimise out the overhead of the wrapper class in many cases. That leaves implicit conversion. But Kotlin doesn't even auto-convert Int to Long. I doubt that implicit conversion will ever be a part of the language outside of a few special cases like toString(). I guess for my use cases, a wrapper class + an extension method on the existing type that adds a toFoo() is sufficient.
- norswap 11y agoThe point of the class instance (the "singleton") is not performance, it's that it exists independently of any instance of the type, so it can be used for factories and such. This however, is not my main concern, and as you remarked, it does not fit very well in OO, which is why it isn't in my proposal. On to the interesting part. You want multiple adapters, rather than multiple interfaces, precisely because you want retroactive interface implementation. That's really the crux here. So either you pass two adapters, or you make a class combining the two adapters. I think both solution create too much bloat to be acceptable. For me, equality isn't so rare. Both identity (==) and structural equality (equals(), done right) pop up with some regularity. Each time you use a map, for instance. And functional languages are perfectly capable of structural equality. While Scala typeclases are implemented with implicit objects, typeclasses precisely avoid using implicit conversions. You still have the original object, the typeclasses just provides functions to work with it. It's only an implicit conversion in the same sense that passing an ArrayList to a function expecting a List is implicit conversion. This is manifest in Scala where you require a typeclass using some sort of type bound.
- mike_hearn 11y agoThanks for the explanations. Yes, there seems to be an impedence mismatch when trying to import the concept of Haskell type classes to OOP languages. WRT a combined adapter class, it'd look like this in Kotlin: class FooBarAdapter(val x: Baz, private val y = Foo(x), private val z = Bar(x)) : Foo by y, Bar by z A bit bloaty, but still a single line of code. (I know you already know this, I'm writing it here for Kotlin newbies).
- norswap 11y agoWow, actually this hadn't made it to my bag of trick yet. It's very nice, though I can see that becoming quite heavyweight when you nest these calls (either heavy from the overhead, or from the syntax - at least you get to decide).
- lmm 11y agoThe problem there is composability. Foo and Bar might come from different libraries. Also it's very common to define one typeclass in terms of another, often several layers deep. It's easy enough to define a wrapper at an outer layer, but when you have an EitherT[WriterT[Future, MyLog, ?], MyError, MyValue] it becomes quite clunky to apply wrappers to MyLog and Future.