Skip to content
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 reverse method to NonEmptyTuple #13752

Merged
merged 9 commits into from
Jul 3, 2023
Merged

Conversation

danicheg
Copy link
Contributor

In Scala 2, there is a method swap on Tuple2. In Scala 3, we could have it generalized version for any non-empty tuple as a whole.

@@ -185,6 +185,14 @@ object Tuple {
*/
type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F]

/** Type of the reversed tuple */
@experimental
type Reverse[X <: Tuple] <: Tuple = X match {
Copy link
Member

Choose a reason for hiding this comment

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

This should be more efficient

Suggested change
type Reverse[X <: Tuple] <: Tuple = X match {
type Reverse[X <: Tuple] = ReverseImpl[EmptyTuple, X]
@experimental
private type ReverseImpl[Acc <: Tuple, X <: Tuple] <: Tuple = X match {
case x *: xs => ReverseImpl[x *: Acc, xs]
case EmptyTuple => Acc
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I'm not wrong this will lead to the compilation error non-private type Reverse in object Tuple refers to private type ReverseImpl.

Copy link
Member

@bishabosha bishabosha Oct 15, 2021

Choose a reason for hiding this comment

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

yes you are right, thank you, in this case maybe the implementation can be moved to a public object to avoid namespace pollution :/ or rename it ReverseOnto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Speaking for myself, I'm ok with adding that extra type, but I don't think that type could be reusable anywhere else. So probably it's good to hear the opinions of other Tuple contributors. What do you think, @nicolasstucki @anatoliykmetyuk?

Copy link
Contributor

Choose a reason for hiding this comment

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

An alternative that seems to work is

  type Reverse[X <: Tuple] = Fold[X, EmptyTuple, [A <: Tuple, Y] =>> Y *: A]

But it would be better if it would be

  type Reverse[X <: Tuple] <: Tuple = Fold[X, EmptyTuple, [A <: Tuple, Y] =>> Y *: A]

Opened #13813 to track this issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I've tried a variant with Fold and got (tried to compile after clean):

[error] 209 |  type Reverse[X <: Tuple] = Fold[X, EmptyTuple, [A <: Tuple, Y] =>> Y *: A]
[error]     |                                                                 ^
[error]     |Type argument [A <: Tuple, Y] =>> Y *: A does not conform to upper bound [_, _] =>> Any

* consisting all its elements.
*/
@experimental
inline def reverse[This >: this.type <: NonEmptyTuple]: Reverse[This] =
Copy link
Contributor

Choose a reason for hiding this comment

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

Should work for any Tuple

@@ -185,6 +185,14 @@ object Tuple {
*/
type IsMappedBy[F[_]] = [X <: Tuple] =>> X =:= Map[InverseMap[X, F], F]

/** Type of the reversed tuple */
@experimental
type Reverse[X <: Tuple] <: Tuple = X match {
Copy link
Contributor

Choose a reason for hiding this comment

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

An alternative that seems to work is

  type Reverse[X <: Tuple] = Fold[X, EmptyTuple, [A <: Tuple, Y] =>> Y *: A]

But it would be better if it would be

  type Reverse[X <: Tuple] <: Tuple = Fold[X, EmptyTuple, [A <: Tuple, Y] =>> Y *: A]

Opened #13813 to track this issue

Copy link
Contributor

@Decel Decel left a comment

Choose a reason for hiding this comment

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

I think if we apply the proposed changes, it's a good solution. I explained in #13813 why we can't do another approach with Fold.

* consisting all its elements.
*/
@experimental
inline def reverse[This >: this.type <: NonEmptyTuple]: Reverse[This] =
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
inline def reverse[This >: this.type <: NonEmptyTuple]: Reverse[This] =
inline def reverse[This >: this.type <: Tuple]: Reverse[This] =

@@ -9,5 +9,6 @@ object MiMaFilters {
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.append"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeReprMethods.substituteTypes"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TypeReprMethods.substituteTypes"),
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.reverse"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.reverse"),

@danicheg
Copy link
Contributor Author

danicheg commented Apr 6, 2023

If dotty's team is interested in taking this, I'm happy to move on with any changes 👍

@ckipp01
Copy link
Member

ckipp01 commented May 10, 2023

If dotty's team is interested in taking this, I'm happy to move on with any changes 👍

@bishabosha since you were commenting on this I'll ping you. Is this something worth pushing forward? it looks like @Decel is willing if it is.

@ckipp01 ckipp01 requested a review from bishabosha May 10, 2023 08:06
@ckipp01 ckipp01 assigned bishabosha and unassigned nicolasstucki May 10, 2023
@bishabosha
Copy link
Member

I think its worth having, @Decel would you like to try a second attempt?

@Decel
Copy link
Contributor

Decel commented May 11, 2023

I think its worth having, @Decel would you like to try a second attempt?

I think we must finish up here and reverse should be implemented, but we haven't actually decided on the solution:

  1. Keep the match type slow, but clean.

  2. Add a helper match type with an upper bound. Works, but we can't keep the helper private.

  3. Try to allow Wildcards in upper bounds. I don't think we can have that, although I'd be glad to be convinced otherwise.

  4. @dwijnand suggestion: Change Fold definition to

type Fold[Tup <: Tuple, R, Z <: R, F[_, _ <: Tuple] <: R] <: R = Tup match
  case EmptyTuple => Z
  case h *: t => F[h, Fold[t, R, Z, F]]

I think 2 is the best option available. But we need to come up with a design for the helper.

@bishabosha
Copy link
Member

I think 2 is the best option available. But we need to come up with a design for the helper.

object Tuple:

  @experimental
  object Helpers:

    @experimental
    type ReverseImpl[Acc <: Tuple, X <: Tuple] <: Tuple = X match
      case x *: xs    => ReverseImpl[x *: Acc, xs]
      case EmptyTuple => Acc

@Decel
Copy link
Contributor

Decel commented May 11, 2023

object Tuple:

  @experimental
  object Helpers:

    @experimental
    type ReverseImpl[Acc <: Tuple, X <: Tuple] <: Tuple = X match
      case x *: xs    => ReverseImpl[x *: Acc, xs]
      case EmptyTuple => Acc

Looks good. Would you like to commit it to the PR? The only other change I think should be is Tuple as an upper bound of the scrutinee, instead of NonEmptyTuple

@danicheg
Copy link
Contributor Author

fwiw: this definition of Reverse type doesn't work

type Reverse[X <: Tuple] <: Tuple = Helpers.ReverseImpl[EmptyTuple, X]
// ^ cannot combine bound and alias

Am I missing something?

@Decel
Copy link
Contributor

Decel commented May 14, 2023

fwiw: this definition of Reverse type doesn't work

type Reverse[X <: Tuple] <: Tuple = Helpers.ReverseImpl[EmptyTuple, X]
// ^ cannot combine bound and alias

Am I missing something?

We can't write type bounds on aliases:

type Reverse[X <: Tuple] = Helpers.ReverseImpl[EmptyTuple, X]

Since ReverseImpl upper bound is Tuple we will be able to infer the same for Reverse

@danicheg
Copy link
Contributor Author

I've addressed the review comments, thanks for your tips and suggestions. CI is green, so this could be reviewed once again.

@@ -447,6 +449,79 @@ object Tuples {
}
}

// Reverse for TupleXXL
private def xxlReverse(xxl: TupleXXL): Tuple = {
if (xxl.productArity == 22) {
Copy link
Member

Choose a reason for hiding this comment

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

this branch should be removed - TupleXXL should not be constructed unless its arity is 23+

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, indeed. Good call, thanks

@bishabosha
Copy link
Member

bishabosha commented May 17, 2023

Experimental definitions shouldn't make warnings in mima so you can remove those changes to MiMaFilters

@danicheg
Copy link
Contributor Author

Experimental definitions shouldn't make warnings in mima so you can remove those changes to MiMaFilters

Actually, I tried that locally before pushing it to the remote branch, and MiMa didn't like it.

@bishabosha
Copy link
Member

bishabosha commented May 17, 2023

Actually, I tried that locally before pushing it to the remote branch, and MiMa didn't like it.

I need someone else to advise here

@danicheg
Copy link
Contributor Author

If we don't mention anyone from the Scala3 team, this PR will get stuck as it was earlier. We all spent quite a lot of resources to get to this point, so can you @bishabosha @ckipp01 please step into finding the right person to perform a final push? But yeah, it's not an urgent thing at all.

@bishabosha
Copy link
Member

@Kordyjan what is the MiMa policy for experimental definitions?

@Kordyjan
Copy link
Contributor

They need to be listed in the MimaFilters. Everything looks good here!

@danicheg
Copy link
Contributor Author

So, 3.3.0 is published 🎉 . What does it mean for this PR? Can it be released only in 3.4.0?

@bishabosha
Copy link
Member

it can be released as experimental in a patch release

@danicheg
Copy link
Contributor Author

danicheg commented Jun 3, 2023

Good to know! So, I will do the final ping and stop bothering people with this.

* consisting all its elements.
*/
@experimental
inline def reverse[This >: this.type <: Tuple]: Reverse[This] =
Copy link
Member

Choose a reason for hiding this comment

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

This method should also be moved to the Tuple trait, currently it is in NonEmptyTuple

"scala.quoted.Quotes.reflectModule.defnModule.ErasedFunctionClass",

// New feature: reverse method on Tuple
"scala.NonEmptyTuple.reverse",
Copy link
Member

Choose a reason for hiding this comment

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

this will be affected when you move reverse to Tuple

Copy link
Member

@bishabosha bishabosha left a comment

Choose a reason for hiding this comment

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

reverse should be a member of the Tuple trait to match its implementation

@danicheg
Copy link
Contributor Author

danicheg commented Jul 2, 2023

@bishabosha here we go (again)...

Copy link
Member

@bishabosha bishabosha left a comment

Choose a reason for hiding this comment

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

Ok looks good, sorry could we also have a test that checks e.g. that (1, true, "hello"). reverse is typed as (String, Boolean, Int) and try to do a git rebase instead of pull - otherwise looks good

@danicheg
Copy link
Contributor Author

danicheg commented Jul 2, 2023

@bishabosha

could we also have a test that checks e.g. that (1, true, "hello"). reverse is typed as (String, Boolean, Int)

done

try to do a git rebase instead of pull

could you accomplish a squash of commits when merging?

@bishabosha bishabosha merged commit b72dfb5 into scala:main Jul 3, 2023
@danicheg danicheg deleted the tuple-reverse branch July 3, 2023 14:05
@Kordyjan Kordyjan added this to the 3.4.0 milestone Aug 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants