-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add extension clauses #4085
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
Add extension clauses #4085
Conversation
* | ||
* where | ||
* | ||
* <extension-name> = concatenation of all alphanumeric characters between and including `extend` and `{`, |
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.
Let's consider the impact on binary compatibility. Are there valid changes to the signature, which result in a new name, but which should not affect binary compatibility? Generally, I think it's important to give the programmer the ability to specify a name explicitly.
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.
One easy example: exchange a fully-qualified for a relative name or vice versa.
augment List[String] {
would be binary-incompatible with
augment List[java.lang.String] {
under Martin's naming proposal.
Also, renaming a type T
parameter into type U
.
Plenty more like that.
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.
Could use fully qualified names & a positional naming scheme for type params?
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.
I think that would not be what we want. We want variation, in particular in type parameter names, so that we can avoid double definitions.
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.
Sorry, I think what I meant to say was type patterns? The type T
and type U
that @sjrd mentioned above. I think you already have a double definition if you have an extension for type T
and type U
that share the same method name.
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.
I think you already have a double definition if you have an extension for type T and type U that share the same method name.
No that's precisely it - you want to enable multiple extends of the same type. So you should not get a double definition if you change the parameter type.
The blue sky sketch for extension-based typeclasses has at least two intrinsic issues:
and one extrinsic issue:
|
} | ||
|
||
trait HasEql[T] { | ||
def === (that: T) |
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.
Missing : Boolean
def result[T](implicit er: WrappedResult[T]): T = WrappedResult.unwrap(er) | ||
|
||
extend (type T) { | ||
def ensuring[U](condition: implicit WrappedResult[T] => Boolean): T = { |
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.
[U]
unused
Add `opaque` to syntax. Let it be parsed and stored/pickled as a flag.
An opaque type becomes an abstract type, with the alias stored in an OpaqueAlias annotation
maintain the link from a module class to its opaque type companion, using the same technique as for companion classes.
Higher-kinded comparisons did not account for the fact that a type constructor in a higher-kinded application could have a narrowed GADT bound.
The previous scheme, based on "magic" methods could not accommodate links from opaque types to their companion objects because there was no way to put a magic method on the opaque type. So we now use Annotations instead, which leads to some simplifications. Note: when comparing the old and new scheme I noted that the links from companion object of a value class to the value class itself broke after erasure, until they were restored in RestoreScopes. This looked unintended to me. The new scheme keeps the links unbroken throughout.
FirstTransform rewrites opaque type aliases to normal aliases, so no boxing is introduced in erasure.
It's overall simpler to just define them internally in Definitions
Make them part of the OpaqueAlias annotation. This is has two benefits - opaque types stop being companions after FirstTransform. Previously, the LinkedType annotations would persist even though the Opaque flag was reset. - we gain the freedom to implement companions between classes differently.
Use a field in ClassDenotation instead of an annotation
When computing the member denotation of a selection of a TypeRef `T`, if the normal scheme fails and `T` has GADT bounds, compute the member in the upper bound instead. This is needed to make the opaque-probability test work. Add this test, as well as some others coming from SIP 35.
The `findMember` and `underlying` methods on `TermRef` now take GADT bounds into account. This replaces the previous special case in `secectionType`. The previous case did always pass -Ycheck. Currently, the treatment is restricted to TypeRefs of opaque types. We should extend that to all GADT-bound TypeRefs. The question is how this will affect performance.
The GADT bounds set in an opaque companion also need to be established for any inlined code coming from the companion. Test case in opaque-immutable-array.scala.
The syntax is still open to discussion.
Have `type T` as an alternate for lower-case `t` identifiers as variable binders in type patterns. Over time, we will deprecate the `t` form.
Some source of nondeterminism remains in the backend.
Also, display anonymous augmentation names in nicer form.
This was fixed in one of the previous commits that affeted names
Also, drop labels.
- Use `:` instead of `implements`. Still todo: Make the `:` context constraint abstract over such instance declarations.
Allow several type aliases if they are the same.
I might be a bit late to the party here as well as missing some context, but what is the case for using I noticed that that proposal advocates for deprecating What is the general impetus behind making a new augment feature, is it to replace typeclasses (in which case doesn't it make more sense to just make a language feature specifically for typeclass) or is it to make From the onset it seems like there isn't a coherent goal that |
|
||
extend String : Monoid { | ||
static def unit = "" | ||
def + (that: Int) = this ++ that |
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.
that: String
surely?
Closed in favor of #4114 |
@mdedetrich The motivation for this work is SIP 35 and #4028. To avoid feature duplication, it would be nice if we could remove value classes once we have opaque types. Value classes have proven to be somewhat a can of worms, because of complicated restrictions and a sometimes surprising boxing model. But extension methods rely on value classes. Hence, the question: Can we find a more direct way to express extension methods? That's the first level. The other, somewhat independent question is whether we can or should extend the scheme to do extension methods to also do instance declarations in the sense of Rust. People's opinions are so far divided on this (which means, most are against, and I am holding out :-) |
This is an evolution of #4043.
Prompted by the discussion there, I made three major changes
augment/extends
toextend/implement
and in a later commit toextend/: