-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Pre-SIP: Improvements to Type Classes #19395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Oooh, I like it. I can quibble with a detail or two, but overall, from the user (and teacher) POV these look like major wins... |
@ritschwumm @dwijnand Thanks for reporting the typos! |
* @complexity | ||
* O(1) | ||
*/ | ||
def headAndTail: Option[(Self.Element, Slice[Self])] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a cool observation: so this is type member selection on a synthetic Collection
term parameter called Self
, i.e. not the Self
type, very elegant
what about making it even more clear and concise by replacing
with just one-liner:
? |
That's not clearer, and requires more explanations. Where does the |
It comes straight from your proposal stated in docs/_docs/reference/experimental/typeclasses.md; if it is a type class, it has a type member named
|
My proposal is irrelevant for people reading the code. |
If someone changes a type member name from |
no, the context bound desugaring requires a member called |
@bishabosha, so this is still something non-trivial to learn, and IMHO, the separate (soft) keyword would convey the intention better than the trait + |
I think it is great to look at how type classes can be improved. Here are some of my (unasked) two cents:
|
This is likely to be highly subjective, but in my opinion, "a trait with a Self type" is more simple than a new category of things, because all the other components that make it behave as a type class are explained in terms of other things you may already know. "what is I guess you could say the same about the context bound syntax being opaque at first glance, but then you will see the explanation of desugaring to real things that you can see. |
@bishabosha Really? IMHO "a trait with a Self type" is becoming a totally new category of things with this PR, not just "a trait with a Self type" |
Imagine the ridicule: Only scala has "types" and "classes" and "typeclasses", but in fact "typeclasses" are neither types nor classes, they are traits! No, that won't fly. |
@odersky you can say the same about |
The If you bake in the |
There are many good things in this proposal. But, to be honest, I would rather prefer one of the following things instead of
|
I have to agree with the comment that
|
Although given that the whole point of e.g. what if we say |
I hesitated a lot between For instance, here Int is Monoid
List is Monad
Int is Ordered
List[T] is Collection
Int is Readable
String is Writable But here Int has upickle.default.ReadWriter
String has mainargs.TokensReader
Boolean has scalasql.TypeMapper
Int has Ordering On the other hand, the type class Int is upickle.default.ReadWriter example, it's also weird if the ReaderWriter was expanded like this: ReadWriter { type Self = Int }
So in summary I think that |
I've had another thought which is how this will interact with |
If we are talking about the classic typeclasses borrowed from category theory I like Int forms Monoid
List forms Monad
Well, in fact one could even say But the cool thing about this proposal is that if someone feels too strongly about it, they could create the preferred type alias in user space. P.S. An example where the current ecosystem is breaking the conventions - |
Co-authored-by: Dale Wijnand <[email protected]>
Co-authored-by: Dale Wijnand <[email protected]>
This is a trial balloon to see whether `forms` works better than `this`. My immediate reaction is meh. Sometimes it's OK, at other times I liked `is` better. But I admit there's bias since the examples were chosen to work well with `is`.
This is a trial balloon to see whether `forms` works better than `this`. My immediate reaction is meh. Sometimes it's OK, at other times I liked `is` better. But I admit there's bias since the examples were chosen to work well with `is`.
This is a trial as a first step for other refactorings down the line.
This if for better understandability only since it avoids the deeply nested return. Also, add a new test showing how to deal with tracked parameters in typeclass arguments.
Shows choice between parameterized and higher-kinded type classes.
That way they can be implemented by operations of the form `given T = f(...)`, which also map to lazy values. And strict abstract values cannot be overridden by lazy values since that could undermine realizability and with it type soundness.
1. Use the constrained type's name as a term name only for single context bounds 2. Apply the same scheme to deferred givens
When expanding a context bound of a member type, don't use the member name as the name of the generated deferred given. The same member type might have different single context bounds in different traits. A class inheriting several of these traits would then get double definitions in its given clauses. Partial revert of "Changes to default names for context bound witnesses"
Consider the following program: ```scala class A class B extends A class C extends A given A = A() given B = B() given C = C() def f(using a: A, b: B, c: C) = println(a.getClass) println(b.getClass) println(c.getClass) @main def Test = f ``` With the current rules, this would fail with an ambiguity error between B and C when trying to synthesize the A parameter. This is a problem without an easy remedy. We can fix this problem by flipping the priority for implicit arguments. Instead of requiring an argument to be most _specific_, we now require it to be most _general_ while still conforming to the formal parameter. There are three justifications for this change, which at first glance seems quite drastic: - It gives us a natural way to deal with inheritance triangles like the one in the code above. Such triangles are quite common. - Intuitively, we want to get the closest possible match between required formal parameter type and synthetisized argument. The "most general" rule provides that. - We already do a crucial part of this. Namely, with current rules we interpolate all type variables in an implicit argument downwards, no matter what their variance is. This makes no sense in theory, but solves hairy problems with contravariant typeclasses like `Comparable`. Instead of this hack, we now do something more principled, by flipping the direction everywhere, preferring general over specific, instead of just flipping contravariant type parameters. The behavior is dependent on the Scala version - Old behavior: up to 3.4 - New behavior: from 3.5, 3.5-migration warns on behavior change The CB builds under the new rules. One fix was needed for a shapeless 3 deriving test. There was a typo: mkInstances instead of mkProductInstances, which previously got healed by accident because of the most specific rule. Also: Don't flip contravariant type arguments for overloading resolution Flipping contravariant type arguments was needed for implicit search where it will be replaced by a more general scheme. But it makes no sense for overloading resolution. For overloading resolution, we want to pick the most specific alternative, analogous to us picking the most specific instantiation when we force a fully defined type. Also: Disable implicit search everywhere for disambiaguation Previously, one disambiguation step missed that, whereas implicits were turned off everywhere else.
Also, fix the unmangling of UniqueExtNames, which seemingly never worked.
Add a config setting whether or not to use the type name for unary context bounds as default name. It's on by default. If it is off, context bound companions are created instead. After fixing several problems, the test suite was verified to compile with the setting set to off.
762d69f
to
3fe93ce
Compare
3fe93ce
to
dc8c708
Compare
Align handling of tracked with constructors. This means - Do it in Namer instead of Desugar - Only add tracked to context bound witnesses if they have abstract type members.
Superseded by #20061 |
Various proposed improvements to how Scala handles generic programming and type classes. They are summarized in this doc page:
https://github.com/dotty-staging/dotty/blob/typeclass-experiments/docs/_docs/reference/experimental/typeclasses.md
This PR is based on #18958.