Change Either subclasses type parameters?

I think we need a SIP for changes on core classes like this, not only a PR.

The current design is inconsistent:

  • None has zero type parameter, while its super type Option has one.
  • Left and Right have the same type parameters as their super type Either.
  • Failure has the same type parameter as their super type Try.

I thought the consistency is the strongest reason for this change.


Note that this change could be almost source compatible with the help of opaque types.

package util {
  enum Either[+L, +R] {
    case Left(l: L) extends Either[L, Nothing]
    case Right(r: R) extends Either[Nothing, R]
  }
}

package object util {

  trait OpaqueTypes {
    type Left[+L, +R] <: Either.Left[L]
  }
  
  val opaqueTypes: OpaqueTypes = new OpaqueTypes {
    type Left[+L, +R] = Either.Left[L]
  }
  
  type Left[+L, +R] = opaqueTypes.Left[+L, +R] 
  
  object Left {
    def apply ...
    def unapply ...
  }
}

1 Like

Don’t take my word for it, but I’m not so sure.

Also a good PR helps a SIP anyways so there’s reason to do one nonetheless, IMO.

The collections redesign didn’t go through the SIP process, so …

1 Like

What do people think of this open PR https://github.com/scala/scala/pull/6329 for Either?
it will allow writing things like Either.left[Int]("empty") and that would be typed as Either[String, Int]

2 Likes

I’ve now asked about this on Stack Overflow: Why do Left and Right have two type parameters?

3 Likes

It’s an innocent and historical mistake that both Left and Right take two type parameters instead of one, and it leads to countless difficulties with type inference.

There are no advantages for the current definitions other than being backwards compatible with existing code. They are disadvantageous in every other way.

3 Likes

Would it be better to create a new class Result that can either be a Success or Failure with sensible type parameters?

I.e. calling stuff Left or Right and then having a convention that one is the success path and the other is an error path seems like unnecessary obfuscation.

4 Likes

See:

1 Like

I also believe Either should be improved or superseded by something else. It will take a while, since the standard library is effectively frozen for 2.13 and 3.0. But it’s good to keep discussing this so that we have something ready when the next iteration of the standard library is due. 2.13 would have been a good opportunity to change it, but that opportunity was not taken up. Let’s make sure we will have a plan next time.

6 Likes

I wonder how often Either will even be used once union types arrive. Aren’t union types superior in every way? I doubt that I would even want an alias type Either[L, R] = L | R. An alias like this removes commutativity but I can’t come up with a use case for this.

Either is different from union types on purpose, e.g. by design Either[A, A] is not the same as A, and Either[A, B] is not the same as Either[B, A]. The typical use case for Either is to distinguish between two outcomes, like a failure and a success, with a different payload type for each outcome. So part of the function of an Either is to be able to ask whether it was failure or success.

6 Likes

IMO, the idea of adding a Result type, as mentioned above, seems apt as a project to explore in scala-library-next. Changing the definition of Either is a slight hassle, but let’s get real: Either has always caused a measure of confusion anyway, because the notion of Right == “correct” is firmly embedded in custom, but not obvious from the naming.

So adding a new type that gets the signatures correct looks like the best plan: it causes minimal disruption, and provides a better path forward for the future.

6 Likes

We can still remove the unnecessary type parameters: https://github.com/scala/scala/pull/9303
It’s a binary compatible change (but obviously not source compatible).
Is it too late to do so?

We can do a community build run on your PR and see what breaks. But I fear we’ll end up concluding that although the change is binary compatible, it will break too much source code to be a possible change for 2.13/3.0.

2 Likes

At https://github.com/scala/scala/pull/9303#issuecomment-739778623 , Lukas says the change isn’t truly binary compatible, so that means it’s dead for 2.13.x.

The community build results were relatively encouraging: https://github.com/scala/scala/pull/9303#issuecomment-739979577

So changing this remains a possibility, but not until Scala 3.T. (Which is our code name for “the first version of Scala 3 or 4 where changes to the standard library can be made.”)

1 Like

Is it called binary compatibility if it’s a compile time issue?

I had that same question, but after discussing it with Dale, I became convinced that the answer is yes. See my remarks about this at https://github.com/scala/scala/pull/9303#issuecomment-740062354

2 Likes