Skip to content

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

Closed
wants to merge 50 commits into from
Closed

Add extension clauses #4085

wants to merge 50 commits into from

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Mar 8, 2018

This is an evolution of #4043.

Prompted by the discussion there, I made three major changes

  • change syntax from augment/extends to extend/implement and in a later commit to extend/:
  • drop labels for extends clauses
  • add scheme to generate stable names for extensions

*
* where
*
* <extension-name> = concatenation of all alphanumeric characters between and including `extend` and `{`,
Copy link
Contributor

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.

Copy link
Member

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.

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?

Copy link
Contributor Author

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.

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.

Copy link
Contributor Author

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.

@odersky odersky mentioned this pull request Mar 8, 2018
@sjrd
Copy link
Member

sjrd commented Mar 9, 2018

The blue sky sketch for extension-based typeclasses has at least two intrinsic issues:

  • It fundamentally creates many more wrappers at run-time, which increases memory pressure and decreases performance, compared to the existing typeclass mechanism of Scala
  • It does not address @OlivierBlanvillain's example with a single parameter having to honor two different typeclasses (HasArea and HasShape in that example).

and one extrinsic issue:

  • The purely-functional ecosystem of Scala is too large now to do a 180° turn and go back to a completely different, incompatible way of encoding typeclasses in Scala.

}

trait HasEql[T] {
def === (that: T)
Copy link
Contributor

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 = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[U] unused

odersky added 22 commits March 12, 2018 11:35
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.
odersky added 2 commits March 14, 2018 15:42
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.
Allow several type aliases if they are the same.
@mdedetrich
Copy link

mdedetrich commented Mar 14, 2018

I might be a bit late to the party here as well as missing some context, but what is the case for using augment rather than implicit classes?

I noticed that that proposal advocates for deprecating implicit classes in favor of "extension methods" (or augments as the PR says), what exactly are augments gaining over implicit classes to warrant this?

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 implicit classes nicer?

From the onset it seems like there isn't a coherent goal that augment is trying to achieve. I can't figure out if its just trying to rename implicit classes to augment and doing some quality of life changes or if its trying to achieve something more (such as a proper encoding of typeclasses)?


extend String : Monoid {
static def unit = ""
def + (that: Int) = this ++ that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that: String surely?

@odersky odersky mentioned this pull request Mar 15, 2018
@odersky
Copy link
Contributor Author

odersky commented Mar 15, 2018

Closed in favor of #4114

@odersky odersky closed this Mar 15, 2018
@odersky
Copy link
Contributor Author

odersky commented Mar 15, 2018

@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 :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants