Whould it make sense to remove Context bound syntax and implicitly and support ad-hoc directly by T<: U?

99 views
Skip to first unread message

Petr Novak

unread,
Oct 6, 2016, 11:21:29 AM10/6/16
to scala-debate
It would allow to use types for which required interface is defined by typeclasses seamlessly in method requiring [T <: U].
It would allow to use simpler direct subtyping using extends for types which I have under control instead of typeclass pattern.

It would be just defined that subtyping can be declared either explicitly using extends or ad-hoc using typeclass pattern. Why we treat them as distinct things when both are just about satisfying required interface.

I could have:
def agg[T <: Numeric](x: T) = x.plus(...)

Then define my type either as class MyType extends Numeric, or define ad-hoc typeclass for it. If compiler would found that T doesn't extends Numeric it would search its implicit Numeric instance.

def agg[T <: Numeric](x: T) and def agg[T : Numeric](x: T) are not compatible right now even my type effectively defines interface which both requires.

There wouldn't be need for implicitly either. Either helper with allows to use Numeric[T].plus(...) is more verbose and requires more function calls.

Does it make sense?

Petr




Justin du coeur

unread,
Oct 6, 2016, 1:31:13 PM10/6/16
to Petr Novak, scala-debate
In principle I can see some appeal to this, but it feels like it gets awfully messy when you start looking at the details.  I mean, just to begin with, the methods usually aren't *shaped* the same way on subclasses vs. typeclasses -- the subclass usually assumes this object as a parameter, whereas the typeclass has to spell it out explicitly.  Consider that nobody actually subclasses a numeric type from Numeric, because the interface is shaped like a typeclass.

And then there are the compiler implications.  Consider: *invoking* a method directly on an object is very different from invoking a typeclass *with* that object.  In principle they're both function calls where the object in question is a privileged parameter, but I'd be a little surprised if they look that much alike in the generated JVM code.  And since you're saying that the decision about which one to use is made at the *call site*, that means that the method itself has to always be set up to handle either -- indeed, *every* method that takes a subtyped parameter must always be compiled to do so.

I mean, implicitly isn't there just because people like having more verbiage.  It's dealing with the fact that a typeclass is actually a *different* object than the one you're invoking it on.  That's not a small detail.

So I'm a bit skeptical.  It's an intriguing concept, and I'm curious what the folks who know the compiler better think of it, but I doubt it would be worth the effort involved in making it happen (and, I suspect, would cause an across-the-board performance hit)...

--
You received this message because you are subscribed to the Google Groups "scala-debate" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-debate+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Petr Novak

unread,
Oct 12, 2016, 1:35:01 PM10/12/16
to scala-debate, oss.m...@gmail.com
Thank you for the info. The actual implementation is probably problematic. But I'm still thinking if it would make sense conceptually. Or it actually any language in history attempted this approach. Having single universal for all three options - subtyping,implicit conversion (function), implicit value (typeclass) - how to make types compatible to allow for a single polymorfic function definitions. For me it is just "wiring" behind the scene whenever I can't subtype or I have no control over source code. But I can miss some deeper points which would make it impractical and totally confusing.

Cheers,
Petr.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-debate...@googlegroups.com.

Petr Novak

unread,
Oct 13, 2016, 4:21:52 AM10/13/16
to scala-debate, oss.m...@gmail.com
Or it could be approached the other way around and leave [T <: U] as is. And define that [T : U] can be satisfied by any means supported to make types substitutable, which is either subtyping by directly extending trait U<T>, or implicit val for trait U<T> (typeclass) or implicit conversion by function T => U<T>.

This way way I could define trait U<T> for typeclass but internally implemented it by subtyping for my types in my lib, which is more straigtforward and avoids implicits for faster compile times. I could use implicit val whenever it wouldn't make sense to subtype. Lib API generic functions would be based on [T : U] so that users could adapt any type to it. And there still would be an option to provide base type in API which could be implemented both way by users.

This way seems to me as possible to implement on compiler level, but I have little to none knowledge about it.

Treat it just as an idea. I would like to further understand the topic and pointing out why it would be bad design and its weaknesses can help me to understand it from another angle.

Cheers,
Petr

Petr Novak

unread,
Oct 22, 2016, 5:38:11 AM10/22/16
to scala-debate, oss.m...@gmail.com
The shape difference between traits for subtyping and traits for typeclasses is a problem though. It reminds me of Single Access Principle. It looks to me it would be nice to define that when type supports some methods they are always called directly on its instance, regardless if they are implemented via subtyping or typeclass so that we wouldn't have to acquire implicit instance for a typeclass.

It is kind of like inverse Self Types where we promise that some traits will be mixed in via extends/with at some point. With typeclass we could promise this trait will be used together with instance of a type the typeclass is implemented for, hence its instance could be implicit and wouldn't need to be passed in explicitly as argument to methods. It means that traits for typeclasses could have the same shape as traits for subtyping.

Typeclass type parameter would serve to mark for which type it is implemented. Or there could be specialized syntax, e.g. "trait Show for Person {}", similarly to Rust. Or "trait Show { Person => ... }" but this could collide with already existent self types. Person is some type we have no control over and needs to have some additional functionality implemented.

Rust uses traits whose methods takes &self as first arguments. Then it uses "impl Show for Person" syntax to implemented, &self provides Person instance. Show methods than can be called directly on Person instance.

Right now I default to typeclass pattern and generic methods with context bound anytime they need to be public for external use. Which probably good anyway.

It seems to me more natural to have "&self" implicit on JVM to have the same shape as traits for subtyping. Compiler would have to syntetize it which I have no idea if possible.

Petr

Petr Novak

unread,
Oct 22, 2016, 5:51:00 AM10/22/16
to scala-debate, oss.m...@gmail.com
But it would probably lead to some issues of the same kind as multiple inheritance. Typeclasses from different libs, having the same method signatures would collide. The current Scala approach avoids that because methods are always namespaced under separate instance.

Petr
Reply all
Reply to author
Forward
0 new messages