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

Right-bias Either #5135

Merged
merged 2 commits into from Jul 12, 2016

Conversation

Projects
None yet
@soc
Member

soc commented Apr 27, 2016

  • Add operations like map, flatMap which assume right-bias
  • Deprecate {Left,Right}Projection
  • Deprecate left and right in favor of swap
  • Add contains, toOption, toTry, toSeq and filterOrElse
  • toSeq returns collection.immutable.Seq instead of collection.Seq
  • Don't add get

There are no incompatible changes.
The only possibility of breakage that exists is when people have added
extension methods named map, flatMap etc. to Either in the past doing
something different than the methods added to Either now.

One detail that moved the scales in favor of deprecating LeftProjection
and RightProjection was the desire to have toSeq return
scala.collection.immutable.Seq instead of scala.collection.Seq
like LeftProjection and RightProjection do.
Therefore keeping LeftProjection and RightProjection would introduce
inconsistency.

filter is called filterOrElse because filtering in a for-comprehension
doesn't work if the method needs an explicit argument.

contains was added as safer alternative to
if (either.isRight && either.right.get == $something) ...

While adding filter with an implicit zero value is possible, it's
dangerous as it would require that developers add a "naked" implicit
value of type A to their scope and it would close the door to a future
in which the Scala standard library ships with Monoid and filter could
exist with an implicit Monoid parameter.

@scala-jenkins scala-jenkins added this to the 2.12.0-M5 milestone Apr 27, 2016

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc Apr 27, 2016

Member

Discuss!

Some notes:

  • filter is called filterOrElse because filtering in a for-comprehension doesn't work if the method needs an explicit argument, and I don't want to close the door to a future in which the Scala standard library ships with Monoid and filter could exist with an implicit Monoid parameter.
  • There is a lot of code doing no-ops like case Left(a) => Left(a) replacing it with this would require a cast. Not sure what's the right decision between efficiency and code beauty is ...
Member

soc commented Apr 27, 2016

Discuss!

Some notes:

  • filter is called filterOrElse because filtering in a for-comprehension doesn't work if the method needs an explicit argument, and I don't want to close the door to a future in which the Scala standard library ships with Monoid and filter could exist with an implicit Monoid parameter.
  • There is a lot of code doing no-ops like case Left(a) => Left(a) replacing it with this would require a cast. Not sure what's the right decision between efficiency and code beauty is ...
@smarter

This comment has been minimized.

Show comment
Hide comment
@smarter

smarter Apr 27, 2016

Contributor

There is a lot of code doing no-ops like case Left(a) => Left(a) replacing it with this would require a cast.

Can't you do something like this match { case left @ Left(_) => left; ... }

Contributor

smarter commented Apr 27, 2016

There is a lot of code doing no-ops like case Left(a) => Left(a) replacing it with this would require a cast.

Can't you do something like this match { case left @ Left(_) => left; ... }

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc Apr 27, 2016

Member

@smarter the problem is the other case: If Right as any kind of transformation, the type of Left doesn't fit anymore (Left and Right both carry both type params).

If you have e. g. map with R => S then the result is Either[L, S], but Left is still Either[L, R].

Member

soc commented Apr 27, 2016

@smarter the problem is the other case: If Right as any kind of transformation, the type of Left doesn't fit anymore (Left and Right both carry both type params).

If you have e. g. map with R => S then the result is Either[L, S], but Left is still Either[L, R].

@Sciss

This comment has been minimized.

Show comment
Hide comment
@Sciss

Sciss Apr 27, 2016

I think the problem is if you want to introduce right-bias, you should also redefine Left as Either[A, Nothing]. The following package works as @smarter suggested:

sealed trait Either[+A, +B]

final case class Left[+A](a: A) extends Either[A, Nothing]

final case class Right[+B](b: B) extends Either[Nothing, B]

implicit class EitherOps[A, B](in: Either[A, B]) {
  def flatMap[AA >: A, Y](f: B => Either[AA, Y]): Either[AA, Y] = in match {
     case l @ Left (_) => l
     case     Right(b) => f(b)
  }

  def map[Y](f: B => Y): Either[A, Y] = in match {
    case l @ Left (_) => l
    case     Right(b) => Right(f(b))
  }
}

Sciss commented Apr 27, 2016

I think the problem is if you want to introduce right-bias, you should also redefine Left as Either[A, Nothing]. The following package works as @smarter suggested:

sealed trait Either[+A, +B]

final case class Left[+A](a: A) extends Either[A, Nothing]

final case class Right[+B](b: B) extends Either[Nothing, B]

implicit class EitherOps[A, B](in: Either[A, B]) {
  def flatMap[AA >: A, Y](f: B => Either[AA, Y]): Either[AA, Y] = in match {
     case l @ Left (_) => l
     case     Right(b) => f(b)
  }

  def map[Y](f: B => Y): Either[A, Y] = in match {
    case l @ Left (_) => l
    case     Right(b) => Right(f(b))
  }
}
@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc Apr 27, 2016

Member

@Sciss I agree with you, but this would break every single code base where someone wrote Left[L, R] or Right[L, R].

Member

soc commented Apr 27, 2016

@Sciss I agree with you, but this would break every single code base where someone wrote Left[L, R] or Right[L, R].

@Sciss

This comment has been minimized.

Show comment
Hide comment
@Sciss

Sciss Apr 27, 2016

You could also safely use asInstanceOf[Left[AA, Y]] - it's ugly as hell, but scala-collections is already full of it...

Sciss commented Apr 27, 2016

You could also safely use asInstanceOf[Left[AA, Y]] - it's ugly as hell, but scala-collections is already full of it...

@Sciss

This comment has been minimized.

Show comment
Hide comment
@Sciss

Sciss Apr 27, 2016

Why not deprecate Either in 2.12, add a correct implementation e.g. Or, and then in 2.14 (?) we drop the problematic Either?

Sciss commented Apr 27, 2016

Why not deprecate Either in 2.12, add a correct implementation e.g. Or, and then in 2.14 (?) we drop the problematic Either?

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc Apr 27, 2016

Member

@Sciss Might be possible. I just want to avoid making library author's lives who want to support 2.10-2.1x harder than necessary.

Member

soc commented Apr 27, 2016

@Sciss Might be possible. I just want to avoid making library author's lives who want to support 2.10-2.1x harder than necessary.

@EECOLOR

This comment has been minimized.

Show comment
Hide comment
@EECOLOR

EECOLOR Apr 27, 2016

Contributor

I might have missed it, but it seems it's missing the withFilter. This is required if you want to extract things in a for comprehension:

for {
  (a, b) <- ...
} yield ...

Oh wait, this is probably problematic... what to do when A => Boolean returns false...

I wished Scala made the distinction between extracting tuples or case classes and guards or pattern matching.

Contributor

EECOLOR commented Apr 27, 2016

I might have missed it, but it seems it's missing the withFilter. This is required if you want to extract things in a for comprehension:

for {
  (a, b) <- ...
} yield ...

Oh wait, this is probably problematic... what to do when A => Boolean returns false...

I wished Scala made the distinction between extracting tuples or case classes and guards or pattern matching.

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc Apr 27, 2016

Member

@EECOLOR withFilter has the same problem as filter.

Member

soc commented Apr 27, 2016

@EECOLOR withFilter has the same problem as filter.

Show outdated Hide outdated src/library/scala/util/Either.scala
*
* {{{
* Right(12).right.foreach(x => println(x)) // prints "12"
* Left(12).right.foreach(x => println(x)) // doesn't print

This comment has been minimized.

@xuwei-k

xuwei-k Apr 28, 2016

Contributor
Right(12).foreach(x => println(x)) // prints "12"
Left(12).foreach(x => println(x))  // doesn't print
@xuwei-k

xuwei-k Apr 28, 2016

Contributor
Right(12).foreach(x => println(x)) // prints "12"
Left(12).foreach(x => println(x))  // doesn't print
@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 7, 2016

Member

Updated!

Things to discuss:

  • Add get to Either?
    Most opinions: "no"
  • Added toTry. Good/Bad? (Should it special-case fatal exceptions?)
    Opinions somewhere between "don't care" and "sure, why not?"
  • Should RightProjection be deprecated?
    Opinions generally in favor
  • Should LeftProjection be deprecated? (In favor of swap)
    Opinions generally in favor, but less so than for deprecating RightProjection
  • More precise return type for Either#toSeq? (collection.immutable.Seq instead of collection.Seq) Opinions varied, usually along the lines of "would be nice if the inconsistency with LeftProjection and RightProjection doesn't become to apparent" -> Reason for deprecating LeftProjection and RightProjection?
  • Should we switch all cases to check Right first?
    Mostly, I flipped cases for Either and RightProjection, keeping LeftProjection as-is.
Member

soc commented May 7, 2016

Updated!

Things to discuss:

  • Add get to Either?
    Most opinions: "no"
  • Added toTry. Good/Bad? (Should it special-case fatal exceptions?)
    Opinions somewhere between "don't care" and "sure, why not?"
  • Should RightProjection be deprecated?
    Opinions generally in favor
  • Should LeftProjection be deprecated? (In favor of swap)
    Opinions generally in favor, but less so than for deprecating RightProjection
  • More precise return type for Either#toSeq? (collection.immutable.Seq instead of collection.Seq) Opinions varied, usually along the lines of "would be nice if the inconsistency with LeftProjection and RightProjection doesn't become to apparent" -> Reason for deprecating LeftProjection and RightProjection?
  • Should we switch all cases to check Right first?
    Mostly, I flipped cases for Either and RightProjection, keeping LeftProjection as-is.
@japgolly

This comment has been minimized.

Show comment
Hide comment
@japgolly

japgolly May 7, 2016

Contributor

Here's a tiny nit-pick, when using right-biased either the most frequent path is generally the success path (i.e. the Right path). Therefore it would be slightly better performance to switch most of the cases from

case Left (_) => _
case Right(_) => _

to

case Right(_) => _
case Left (_) => _

because IIUC Scala will check each case sequentially. One of those things you'd never notice would can add up after a large number of calls.

Contributor

japgolly commented May 7, 2016

Here's a tiny nit-pick, when using right-biased either the most frequent path is generally the success path (i.e. the Right path). Therefore it would be slightly better performance to switch most of the cases from

case Left (_) => _
case Right(_) => _

to

case Right(_) => _
case Left (_) => _

because IIUC Scala will check each case sequentially. One of those things you'd never notice would can add up after a large number of calls.

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 11, 2016

Member

@japgolly I flipped the checks in the Either and Right types, left Left as is. I think this should be the optimal configuration to have the first check succeed.

Member

soc commented May 11, 2016

@japgolly I flipped the checks in the Either and Right types, left Left as is. I think this should be the optimal configuration to have the first check succeed.

@som-snytt

This comment has been minimized.

Show comment
Hide comment
@som-snytt

som-snytt May 11, 2016

Contributor

@japgolly b/c "In the interest of efficiency the evaluation of a pattern matching expression may try patterns in some other order than textual sequence." I'd be interested in knowing whether the compiler does the right thing when there are just two choices.

Contributor

som-snytt commented May 11, 2016

@japgolly b/c "In the interest of efficiency the evaluation of a pattern matching expression may try patterns in some other order than textual sequence." I'd be interested in knowing whether the compiler does the right thing when there are just two choices.

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 13, 2016

Member

/rebuild

Member

soc commented May 13, 2016

/rebuild

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 17, 2016

Member

Added ScalaCheck tests.

Member

soc commented May 17, 2016

Added ScalaCheck tests.

Show outdated Hide outdated src/library/scala/util/Either.scala
case Left(a) => this.asInstanceOf[Either[A, Y]]
}
/** Returns `None` if this is a `Left` or if the

This comment has been minimized.

@szeiger

szeiger May 18, 2016

Contributor

"Returns this?"

@szeiger

szeiger May 18, 2016

Contributor

"Returns this?"

This comment has been minimized.

@soc

soc May 19, 2016

Member

Is this different from #5135 (comment)?

@soc

soc May 19, 2016

Member

Is this different from #5135 (comment)?

This comment has been minimized.

@szeiger

szeiger May 20, 2016

Contributor

I was referring to "Returns None" in the doc comment. None is an Option. This method returns an Either.

@szeiger

szeiger May 20, 2016

Contributor

I was referring to "Returns None" in the doc comment. None is an Option. This method returns an Either.

This comment has been minimized.

@soc

soc May 20, 2016

Member

Ah, I see, sorry. Looks like I fixed this in a later change.

@soc

soc May 20, 2016

Member

Ah, I see, sorry. Looks like I fixed this in a later change.

Show outdated Hide outdated src/library/scala/util/Either.scala
* Left(12).filterOrElse(_ > 10, -1) // Right(-1)
* }}}
*/
def filterOrElse[BB >: B](p: B => Boolean, zero: => BB): Either[A, BB] = this match {

This comment has been minimized.

@szeiger

szeiger May 18, 2016

Contributor

What's the motivation for adding this method? Neither RightProjection, Option nor Try currently have it.

@szeiger

szeiger May 18, 2016

Contributor

What's the motivation for adding this method? Neither RightProjection, Option nor Try currently have it.

This comment has been minimized.

@soc

soc May 19, 2016

Member

People seem to want filter (as observed from third-party libraries), but as we lack a Monoid type in the standard library, there is no way to make it work in for-comprehensions without introducing a dangerous implicit zero: BB parameter).

Picking a different name allows this method to exist without interfering with for-comprehension and doesn't prevent us from adding a for-comprehension-compatible filter in the future if the std lib ever gets Monoid.

If we want this method, we should discuss whether => B shoud be B instead, and whether we should move the parameter to a different param list...

@soc

soc May 19, 2016

Member

People seem to want filter (as observed from third-party libraries), but as we lack a Monoid type in the standard library, there is no way to make it work in for-comprehensions without introducing a dangerous implicit zero: BB parameter).

Picking a different name allows this method to exist without interfering with for-comprehension and doesn't prevent us from adding a for-comprehension-compatible filter in the future if the std lib ever gets Monoid.

If we want this method, we should discuss whether => B shoud be B instead, and whether we should move the parameter to a different param list...

This comment has been minimized.

@szeiger

szeiger May 20, 2016

Contributor

I see. I assumed the reason why people seem to want filter is only to enable for comprehensions.

@szeiger

szeiger May 20, 2016

Contributor

I see. I assumed the reason why people seem to want filter is only to enable for comprehensions.

@szeiger

This comment has been minimized.

Show comment
Hide comment
@szeiger

szeiger May 18, 2016

Contributor

Is a pattern match the best solution for implementing all these methods? It should compile to an instanceOf check but I would expect polymorphic dispatch to be faster still.

Contributor

szeiger commented May 18, 2016

Is a pattern match the best solution for implementing all these methods? It should compile to an instanceOf check but I would expect polymorphic dispatch to be faster still.

@szeiger

This comment has been minimized.

Show comment
Hide comment
@szeiger

szeiger May 18, 2016

Contributor

My take on the discussion points:

  • Deprecate RightProjection (not needed anymore)
  • Deprecate LeftProjection (in favor of swap)
  • Make toSeq return an immutable.Seq. toSeq methods in the collections library have varying return types. Generally immutable collections return an immutable.Seq (and declare it as such). Either is immutable so it would be consistent with the other implementations.
Contributor

szeiger commented May 18, 2016

My take on the discussion points:

  • Deprecate RightProjection (not needed anymore)
  • Deprecate LeftProjection (in favor of swap)
  • Make toSeq return an immutable.Seq. toSeq methods in the collections library have varying return types. Generally immutable collections return an immutable.Seq (and declare it as such). Either is immutable so it would be consistent with the other implementations.
@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 19, 2016

Member

@szeiger I think @Ichoran is the performance expert ... what do you think, @Ichoran?

Member

soc commented May 19, 2016

@szeiger I think @Ichoran is the performance expert ... what do you think, @Ichoran?

@Ichoran

This comment has been minimized.

Show comment
Hide comment
@Ichoran

Ichoran May 20, 2016

Contributor

@soc - You can't tell in any particular case whether pattern match or instanceof will be faster, but for two cases it's usually equivalent once the JIT compiler gets its hands on it (both are compiled down to fast single-branch code; you don't do generic polymorphic dispatch which involves walking a pointer). If it really matters, you have to benchmark it both ways.

Contributor

Ichoran commented May 20, 2016

@soc - You can't tell in any particular case whether pattern match or instanceof will be faster, but for two cases it's usually equivalent once the JIT compiler gets its hands on it (both are compiled down to fast single-branch code; you don't do generic polymorphic dispatch which involves walking a pointer). If it really matters, you have to benchmark it both ways.

@sjrd

This comment has been minimized.

Show comment
Hide comment
@sjrd

sjrd May 20, 2016

Member

If it doesn't matter for the JVM, I can tell you that the virtual call is faster than the instanceof tests in Scala.js.

Member

sjrd commented May 20, 2016

If it doesn't matter for the JVM, I can tell you that the virtual call is faster than the instanceof tests in Scala.js.

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 20, 2016

Member

@szeiger Ok, fixed the things you mentioned.

  • "Right-bias Either" is the same, just with an implementation bug in filterOrElse and the documentation fixed.
  • "[squash] Add contains" It's basically the same as Option#contains.
  • "Deprecate Either's left, right, {Left,Right}Projection" The deprecations you suggested.
Member

soc commented May 20, 2016

@szeiger Ok, fixed the things you mentioned.

  • "Right-bias Either" is the same, just with an implementation bug in filterOrElse and the documentation fixed.
  • "[squash] Add contains" It's basically the same as Option#contains.
  • "Deprecate Either's left, right, {Left,Right}Projection" The deprecations you suggested.
@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 20, 2016

Member

@sjrd Should I rewrite it? I'm a bit concerned as the implementation gets longer and harder to follow.

Member

soc commented May 20, 2016

@sjrd Should I rewrite it? I'm a bit concerned as the implementation gets longer and harder to follow.

@sjrd

This comment has been minimized.

Show comment
Hide comment
@sjrd

sjrd May 20, 2016

Member

No, keep it like that. Actually I realized that most methods are higher-order, so polymorphic dispatch for those is bad.

The best situation is that of Option, which has only isEmpty and get as polymorphic, and then all other methods are implemented in terms of isEmpty and get, without additional polymorphic dispatch. This is really good because the higher-order methods can be inlined and the closures eliminated, and only the two core methods remain polymorphic. For Either, a similar implementation would be awkward, I guess. So it's probably best to keep the current implementation.

Member

sjrd commented May 20, 2016

No, keep it like that. Actually I realized that most methods are higher-order, so polymorphic dispatch for those is bad.

The best situation is that of Option, which has only isEmpty and get as polymorphic, and then all other methods are implemented in terms of isEmpty and get, without additional polymorphic dispatch. This is really good because the higher-order methods can be inlined and the closures eliminated, and only the two core methods remain polymorphic. For Either, a similar implementation would be awkward, I guess. So it's probably best to keep the current implementation.

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 21, 2016

Member
*** Error in `/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java': free(): invalid pointer: 0x00000001003931a8 ***

WTH Jenkins?

Member

soc commented May 21, 2016

*** Error in `/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java': free(): invalid pointer: 0x00000001003931a8 ***

WTH Jenkins?

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 21, 2016

Member

/rebuild

Member

soc commented May 21, 2016

/rebuild

@szeiger

This comment has been minimized.

Show comment
Hide comment
@szeiger

szeiger May 25, 2016

Contributor

LGTM

Contributor

szeiger commented May 25, 2016

LGTM

@adriaanm

This comment has been minimized.

Show comment
Hide comment
@adriaanm

adriaanm May 25, 2016

Member

This needs a commit message. It should summarize this PR, discuss design goals, motivate the change,...

Member

adriaanm commented May 25, 2016

This needs a commit message. It should summarize this PR, discuss design goals, motivate the change,...

@adriaanm adriaanm added the WIP label May 25, 2016

@adriaanm

This comment has been minimized.

Show comment
Hide comment
@adriaanm

adriaanm May 25, 2016

Member

Adding WIP label until that's taken care of. Thanks!

Member

adriaanm commented May 25, 2016

Adding WIP label until that's taken care of. Thanks!

@adriaanm

This comment has been minimized.

Show comment
Hide comment
@adriaanm

adriaanm May 25, 2016

Member

Thanks for being patient with me here as I play catch up, could someone convince me this not going to break existing code? Did we run the community build against this PR? Was this discussed on scala-user? (I saw the scala-internals discussion, but I fear the audience there is a bit, erm, biased :-))

Member

adriaanm commented May 25, 2016

Thanks for being patient with me here as I play catch up, could someone convince me this not going to break existing code? Did we run the community build against this PR? Was this discussed on scala-user? (I saw the scala-internals discussion, but I fear the audience there is a bit, erm, biased :-))

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc May 25, 2016

Member

Will add a decent commit message! Thanks!

Member

soc commented May 25, 2016

Will add a decent commit message! Thanks!

@mdedetrich

This comment has been minimized.

Show comment
Hide comment
@mdedetrich

mdedetrich Jul 21, 2016

Contributor

I like the sound of getOrLeft, its at least consistent with how Option works

Contributor

mdedetrich commented Jul 21, 2016

I like the sound of getOrLeft, its at least consistent with how Option works

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc Jul 21, 2016

Member

@notxcain Not "standardization" with a big S, what I had in mind was "if we ever introduce an internal trait to prevent signatures drifting apart, would this interact nicely with this extension?" (like what I would have done if we decided to add a left-biased Either: I would had introduced something like Eitherlike to make sure that the signatures of Either and LeftEither couldn't drift apart).

Member

soc commented Jul 21, 2016

@notxcain Not "standardization" with a big S, what I had in mind was "if we ever introduce an internal trait to prevent signatures drifting apart, would this interact nicely with this extension?" (like what I would have done if we decided to add a left-biased Either: I would had introduced something like Eitherlike to make sure that the signatures of Either and LeftEither couldn't drift apart).

@soc

This comment has been minimized.

Show comment
Hide comment
@soc

soc Jul 21, 2016

Member

@mdedetrich I think getOrLeft would be ok, especially because the naming is Either-specific. (So we don't consume a possible name that could be a generalization of getOrElse later on...)

Member

soc commented Jul 21, 2016

@mdedetrich I think getOrLeft would be ok, especially because the naming is Either-specific. (So we don't consume a possible name that could be a generalization of getOrElse later on...)

@SethTisue

This comment has been minimized.

Show comment
Hide comment
@SethTisue

SethTisue Aug 8, 2016

Member

for those interested, there are some afterthoughts on the design, and on the process by which this PR got formulated and merged, at scala/slip#20

Member

SethTisue commented Aug 8, 2016

for those interested, there are some afterthoughts on the design, and on the process by which this PR got formulated and merged, at scala/slip#20

@SethTisue

This comment has been minimized.

Show comment
Hide comment
@SethTisue

SethTisue Sep 6, 2016

Member

good discussion at typelevel/cats#1192 about uptake in Cats and other libraries

Member

SethTisue commented Sep 6, 2016

good discussion at typelevel/cats#1192 about uptake in Cats and other libraries

@ronanM

This comment has been minimized.

Show comment
Hide comment
@ronanM

ronanM Sep 10, 2016

Why there is no leftMap(A => C) : Either[C, B] like in previous version of Xor in Cats ?

def leftMap[C](f: A => C): Either[C, B] = this match {
    case Left(a)      => Left(f(a))
    case r @ Right(_) => r
}

IMHO myEither.leftMap(f) is much simpler than myEither.swap.map(f).swap

ronanM commented Sep 10, 2016

Why there is no leftMap(A => C) : Either[C, B] like in previous version of Xor in Cats ?

def leftMap[C](f: A => C): Either[C, B] = this match {
    case Left(a)      => Left(f(a))
    case r @ Right(_) => r
}

IMHO myEither.leftMap(f) is much simpler than myEither.swap.map(f).swap

@sjrd

This comment has been minimized.

Show comment
Hide comment
@sjrd

sjrd Sep 13, 2016

Member

Quoting myself from #5135 (comment)

Left-biased use cases become harder

To counter this, what about not deprecating .left and .right? So basically provide a purely additive PR.

This would also be good to simplify maintaining a codebase cross-compiling with 2.11 and 2.12. If we deprecate .right in 2.12, how am I supposed to write deprecation-free cross-compiling code? With an additive-only PR, if I need to cross-compile, I can still use .left and .right warning-free. If I only target 2.12, I can start taking advantage of the niceties of right-bias.

Now I can show concretely what I meant by this problem. The first commit of scala-js/scala-js#2590 illustrates the issue (the commit is scala-js/scala-js@491dee1, but this link might become stale). Because 1) right-biased methods do not exist yet in 2.11 (nor 2.10) and 2) .right is deprecated in 2.12, there is no way to write cross-version warning-free code between 2.11 and 2.12. I have to use pattern matching instead, which in this case is much less readable and more verbose.

Member

sjrd commented Sep 13, 2016

Quoting myself from #5135 (comment)

Left-biased use cases become harder

To counter this, what about not deprecating .left and .right? So basically provide a purely additive PR.

This would also be good to simplify maintaining a codebase cross-compiling with 2.11 and 2.12. If we deprecate .right in 2.12, how am I supposed to write deprecation-free cross-compiling code? With an additive-only PR, if I need to cross-compile, I can still use .left and .right warning-free. If I only target 2.12, I can start taking advantage of the niceties of right-bias.

Now I can show concretely what I meant by this problem. The first commit of scala-js/scala-js#2590 illustrates the issue (the commit is scala-js/scala-js@491dee1, but this link might become stale). Because 1) right-biased methods do not exist yet in 2.11 (nor 2.10) and 2) .right is deprecated in 2.12, there is no way to write cross-version warning-free code between 2.11 and 2.12. I have to use pattern matching instead, which in this case is much less readable and more verbose.

@mpilquist

This comment has been minimized.

Show comment
Hide comment
@mpilquist

mpilquist Sep 13, 2016

FWIW, I dealt with this in FS2 under -Xfatal-warnings, -deprecation, and -Ywarn-unused-imports by creating an enrichment on Either that adds the 2.12 methods, implementing those in terms of pattern matching, and putting it in the main fs2 package object (to avoid an unused import warning).

https://github.com/functional-streams-for-scala/fs2/blob/series/0.9/core/shared/src/main/scala/fs2/fs2.scala#L25-L40

mpilquist commented Sep 13, 2016

FWIW, I dealt with this in FS2 under -Xfatal-warnings, -deprecation, and -Ywarn-unused-imports by creating an enrichment on Either that adds the 2.12 methods, implementing those in terms of pattern matching, and putting it in the main fs2 package object (to avoid an unused import warning).

https://github.com/functional-streams-for-scala/fs2/blob/series/0.9/core/shared/src/main/scala/fs2/fs2.scala#L25-L40

@sjrd

This comment has been minimized.

Show comment
Hide comment
@sjrd

sjrd Sep 13, 2016

Member

@mpilquist Yes, I think we're going to do something similar in Scala.js. It seems a bit cleaner than the current verbose code.

Member

sjrd commented Sep 13, 2016

@mpilquist Yes, I think we're going to do something similar in Scala.js. It seems a bit cleaner than the current verbose code.

@dwijnand

This comment has been minimized.

Show comment
Hide comment
@dwijnand

dwijnand Sep 13, 2016

Member

@mpilquist you might want to consider a internal or impl package object (with an empty stub for 2.11) instead unless you're happy for those extension methods to leak into the user-land code via fs2._.

Member

dwijnand commented Sep 13, 2016

@mpilquist you might want to consider a internal or impl package object (with an empty stub for 2.11) instead unless you're happy for those extension methods to leak into the user-land code via fs2._.

@mpilquist

This comment has been minimized.

Show comment
Hide comment
@mpilquist

mpilquist Sep 13, 2016

@dwijnand I think doing so will trigger an unused import warning on 2.12 though. The extension class is marked private[fs2] which should accomplish the same, no? (Ignoring non-Scala clients)

mpilquist commented Sep 13, 2016

@dwijnand I think doing so will trigger an unused import warning on 2.12 though. The extension class is marked private[fs2] which should accomplish the same, no? (Ignoring non-Scala clients)

@dwijnand

This comment has been minimized.

Show comment
Hide comment
@dwijnand

dwijnand Sep 13, 2016

Member

Hmm, not sure.

Member

dwijnand commented Sep 13, 2016

Hmm, not sure.

@SethTisue

This comment has been minimized.

Show comment
Hide comment
@SethTisue

SethTisue Sep 13, 2016

Member

re: warnings, have y'all tried https://github.com/ghik/silencer ?

Member

SethTisue commented Sep 13, 2016

re: warnings, have y'all tried https://github.com/ghik/silencer ?

@sjrd

This comment has been minimized.

Show comment
Hide comment
@sjrd

sjrd Sep 13, 2016

Member

@SethTisue No, I haven't. In theory, it looks interesting. In practice, Scala.js cannot afford to have any Scala dependency besides Scala itself. Otherwise, we wouldn't be able to publish for new Scala versions as quickly as needed. Note that most of the ecosystem transitively depends on Scala.js being published at this point, so we need to stay very close to the root of the dependency graph.

Member

sjrd commented Sep 13, 2016

@SethTisue No, I haven't. In theory, it looks interesting. In practice, Scala.js cannot afford to have any Scala dependency besides Scala itself. Otherwise, we wouldn't be able to publish for new Scala versions as quickly as needed. Note that most of the ecosystem transitively depends on Scala.js being published at this point, so we need to stay very close to the root of the dependency graph.

@SethTisue

This comment has been minimized.

Show comment
Hide comment
@SethTisue

SethTisue Sep 26, 2016

Member

I wonder if we should delay the deprecation of .left and .right until 2.13, to facilitate warning-free 2.11/2.12 cross-building. in my work on the Scala 2.12 community build lately I'm seeing huge amounts of these deprecation warnings. and it's not clear (to me anyway) how satisfying the suggested replacement for .left (namely) (.swap) is in practice. and if you are cross-building, there is no suitable substitute for .right

community: how are y'all feeling about this? now that 2.12.0-RC1 has been out for a few weeks

supporting 2.11/2.12 cross-building was one of our core promises for 2.12

in addition to the comments on this in the last 13 days, see also previous discussion back in June, beginning with #5135 (comment)

Member

SethTisue commented Sep 26, 2016

I wonder if we should delay the deprecation of .left and .right until 2.13, to facilitate warning-free 2.11/2.12 cross-building. in my work on the Scala 2.12 community build lately I'm seeing huge amounts of these deprecation warnings. and it's not clear (to me anyway) how satisfying the suggested replacement for .left (namely) (.swap) is in practice. and if you are cross-building, there is no suitable substitute for .right

community: how are y'all feeling about this? now that 2.12.0-RC1 has been out for a few weeks

supporting 2.11/2.12 cross-building was one of our core promises for 2.12

in addition to the comments on this in the last 13 days, see also previous discussion back in June, beginning with #5135 (comment)

@Ichoran

This comment has been minimized.

Show comment
Hide comment
@Ichoran

Ichoran Sep 26, 2016

Contributor

I would rather not deprecate it immediately. The benefit of right-biasing need not come with such a heavy penalty of changing existing code and living with deprecation warnings. Yes, we'll be tied to .right and .left for longer, but the cost is relatively low if they are things that people don't want to use, and if people do want to use them, why are we taking them away so fast?

Contributor

Ichoran commented Sep 26, 2016

I would rather not deprecate it immediately. The benefit of right-biasing need not come with such a heavy penalty of changing existing code and living with deprecation warnings. Yes, we'll be tied to .right and .left for longer, but the cost is relatively low if they are things that people don't want to use, and if people do want to use them, why are we taking them away so fast?

@mpilquist

This comment has been minimized.

Show comment
Hide comment
@mpilquist

mpilquist Sep 26, 2016

+1 for delaying the deprecation until 2.13. This also has the benefit of giving us some time to consider what other changes (if any) we want to make to Either.

mpilquist commented Sep 26, 2016

+1 for delaying the deprecation until 2.13. This also has the benefit of giving us some time to consider what other changes (if any) we want to make to Either.

@BennyHill

This comment has been minimized.

Show comment
Hide comment
@BennyHill

BennyHill Sep 26, 2016

My 2 cents: Other than two, reported scalac issues, Either changes are the only remaining issues and as noted ^^^ are a killer for cross version compiling, unless we all write some compat code such as pasted by @mpilquist .

Is it not possible to have a standard "2.12 fwd compat" module for 2.10 and 2.11? And/or a backwards compat module for 2.12 so existing code works out-of-the-box? Or in other words, keep the change but have tools and strategies in place for easy migration.

BennyHill commented Sep 26, 2016

My 2 cents: Other than two, reported scalac issues, Either changes are the only remaining issues and as noted ^^^ are a killer for cross version compiling, unless we all write some compat code such as pasted by @mpilquist .

Is it not possible to have a standard "2.12 fwd compat" module for 2.10 and 2.11? And/or a backwards compat module for 2.12 so existing code works out-of-the-box? Or in other words, keep the change but have tools and strategies in place for easy migration.

@milessabin

This comment has been minimized.

Show comment
Hide comment
@milessabin

milessabin Sep 26, 2016

Contributor

It's a shame, but I'm also in favour of postponing the deprecation. I'd like to see support for early migration to the new form though.

Contributor

milessabin commented Sep 26, 2016

It's a shame, but I'm also in favour of postponing the deprecation. I'd like to see support for early migration to the new form though.

@dwijnand

This comment has been minimized.

Show comment
Hide comment
@dwijnand

dwijnand Sep 27, 2016

Member

@mpilquist

@dwijnand I think doing so will trigger an unused import warning on 2.12 though. The extension class is marked private[fs2] which should accomplish the same, no? (Ignoring non-Scala clients)

Yep, seems to do the trick, nice one (I can stop creating impl package objects for these then) 👍

scala> import scala.util._
import scala.util._

scala> val e: Either[String, Int] = Right(1)
e: scala.util.Either[String,Int] = Right(1)

scala> e.map(_ + 1)
<console>:16: error: value map is not a member of scala.util.Either[String,Int]
       e.map(_ + 1)
         ^

scala> import fs2._
import fs2._

scala> e.map(_ + 1)
<console>:19: error: value map is not a member of scala.util.Either[String,Int]
       e.map(_ + 1)
         ^
Member

dwijnand commented Sep 27, 2016

@mpilquist

@dwijnand I think doing so will trigger an unused import warning on 2.12 though. The extension class is marked private[fs2] which should accomplish the same, no? (Ignoring non-Scala clients)

Yep, seems to do the trick, nice one (I can stop creating impl package objects for these then) 👍

scala> import scala.util._
import scala.util._

scala> val e: Either[String, Int] = Right(1)
e: scala.util.Either[String,Int] = Right(1)

scala> e.map(_ + 1)
<console>:16: error: value map is not a member of scala.util.Either[String,Int]
       e.map(_ + 1)
         ^

scala> import fs2._
import fs2._

scala> e.map(_ + 1)
<console>:19: error: value map is not a member of scala.util.Either[String,Int]
       e.map(_ + 1)
         ^
@SethTisue

This comment has been minimized.

Show comment
Hide comment
@SethTisue

SethTisue Sep 28, 2016

Member

we discussed this at a Scala team meeting as well. there seems to be a consensus that the deprecation should be delayed. I'll make a PR for 2.12.0-RC2

Member

SethTisue commented Sep 28, 2016

we discussed this at a Scala team meeting as well. there seems to be a consensus that the deprecation should be delayed. I'll make a PR for 2.12.0-RC2

@BennyHill

This comment has been minimized.

Show comment
Hide comment
@BennyHill

BennyHill Sep 28, 2016

@SethTisue Thanks for the heads up.

Is there a rough ETA for 2.12.0-RC2? And related to that, what would the advice be for a project like cats that has made some unpublished changes around this, but is yet to publish as 2.12.0-RC1? ie should it hold off on a 2.12.0-RC1 release or....?

BennyHill commented Sep 28, 2016

@SethTisue Thanks for the heads up.

Is there a rough ETA for 2.12.0-RC2? And related to that, what would the advice be for a project like cats that has made some unpublished changes around this, but is yet to publish as 2.12.0-RC1? ie should it hold off on a 2.12.0-RC1 release or....?

@adriaanm

This comment has been minimized.

Show comment
Hide comment
@adriaanm

adriaanm Sep 28, 2016

Member

We plan to merge the last few RC2 PRs this week, and release next week,
assuming no new blocker bugs are discovered before the end of the week.
On Wed, Sep 28, 2016 at 13:33 BennyHill notifications@github.com wrote:

@SethTisue https://github.com/SethTisue Thanks for the heads up.

Is there a rough ETA for 2.12.0-RC2? And related to that, what would
the advice be for a project like cats that has made some unpublished
changes around this, but is yet to publish as 2.12.0-RC1? ie should it hold
off on a 2.12.0-RC1 release or....?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#5135 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAFjywkYFRSqWe4G7vq_v39ixvCYdNmQks5qus8zgaJpZM4IRRVH
.

Member

adriaanm commented Sep 28, 2016

We plan to merge the last few RC2 PRs this week, and release next week,
assuming no new blocker bugs are discovered before the end of the week.
On Wed, Sep 28, 2016 at 13:33 BennyHill notifications@github.com wrote:

@SethTisue https://github.com/SethTisue Thanks for the heads up.

Is there a rough ETA for 2.12.0-RC2? And related to that, what would
the advice be for a project like cats that has made some unpublished
changes around this, but is yet to publish as 2.12.0-RC1? ie should it hold
off on a 2.12.0-RC1 release or....?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#5135 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAFjywkYFRSqWe4G7vq_v39ixvCYdNmQks5qus8zgaJpZM4IRRVH
.

@BennyHill

This comment has been minimized.

Show comment
Hide comment
@BennyHill

BennyHill commented Sep 28, 2016

👍

@SethTisue

This comment has been minimized.

Show comment
Hide comment
@SethTisue

SethTisue Sep 28, 2016

Member

Is there a rough ETA for 2.12.0-RC2?

We're hoping to freeze it in the next couple days, so then there will be a nightly which is a release-candidate-candidate. If all goes well, RC2 could conceivably hit Maven Central next week sometime. Keep an eye on https://github.com/scala/scala/milestones/2.12.0-RC2

And related to that, what would the advice be for a project like cats that has made some unpublished changes around this, but is yet to publish as 2.12.0-RC1? ie should it hold off on a 2.12.0-RC1 release or....?

Unless there's a great deal of extra hassle involved, I would still strongly recommend everyone try to publish for 2.12.0-RC1 — especially a popular dependency like Cats. The best way to avoid having to do an RC3 (heaven forbid) is for everyone to be doing as much testing/publishing as possible now.

Member

SethTisue commented Sep 28, 2016

Is there a rough ETA for 2.12.0-RC2?

We're hoping to freeze it in the next couple days, so then there will be a nightly which is a release-candidate-candidate. If all goes well, RC2 could conceivably hit Maven Central next week sometime. Keep an eye on https://github.com/scala/scala/milestones/2.12.0-RC2

And related to that, what would the advice be for a project like cats that has made some unpublished changes around this, but is yet to publish as 2.12.0-RC1? ie should it hold off on a 2.12.0-RC1 release or....?

Unless there's a great deal of extra hassle involved, I would still strongly recommend everyone try to publish for 2.12.0-RC1 — especially a popular dependency like Cats. The best way to avoid having to do an RC3 (heaven forbid) is for everyone to be doing as much testing/publishing as possible now.

SethTisue added a commit to SethTisue/scala that referenced this pull request Sep 29, 2016

don't deprecate Either.left and Either.right yet
for two reasons:
* to facilitate warning-free cross-compilation between Scala 2.11
  and 2.12
* because it's not clear that .swap is a good replacement for .left

Either.right seems almost certain to be deprecated in 2.13.
Either.left's future is uncertain; see discussion (and links to
additional discussions) at scala#5135

@adriaanm adriaanm modified the milestone: 2.12.0-RC1 Oct 29, 2016

@adriaanm adriaanm added the 2.12 label Oct 29, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment