Right-biased Either – Please review/comment on PR

354 views
Skip to first unread message

Simon Ochsenreither

unread,
May 25, 2016, 5:42:40 PM5/25/16
to scala-user
I created a PR to
  • right-bias Either
  • deprecate LeftProjection, RightProjection, left and right
  • add filterOrElse and contains

https://github.com/scala/scala/pull/5135


Comments welcome!

Michael Slinn

unread,
Oct 14, 2016, 11:28:35 PM10/14/16
to scala-user
I'm concerned about what I read in the new Scaladoc for Either:

Either is right-biased, which means that Right is assumed to be the default case to operate on. If it is Left, operations like map, flatMap, ... return the Left value unchanged:

Right(12).map(_ * 2) // Right(24)
Left(23).map(_ * 2)  // Left(23)
Seems that one cannot trust that the result of a map (or flatMap) on this new Either will find a Right to work with. If the two types A and B contained by Either[+A, +B] do not support the same mapped operation, and no implicit conversion is in scope, then there is no problem because the compiler will flag any issues, otherwise weird results may occur unless the user thinks to test for a Right or a Left in some fashion before performing a map or flatMap.

What is the benefit of allowing this behavior?

Mike

Rex Kerr

unread,
Oct 14, 2016, 11:47:25 PM10/14/16
to Michael Slinn, scala-user
It's the same as Option.  You don't worry about testing for None before you map--the whole _point_ is that it only works on the Some branch.

Same deal, now, with Either.  You use map when you want to change the Right branch.  If you want to change the left branch instead, use .swap.map(f).swap. The existing .left.map(f) still works, though deprecation is anticipated.

  --Rex


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Michael Slinn

unread,
Oct 15, 2016, 12:07:17 AM10/15/16
to scala-user, msl...@gmail.com
My understanding of what the docs say is that if you call map on an Either, and it contains a Left, the Left will be returned unchanged.

Option short-circuits a for-expression when a None value is encountered. This new Either seems to short-circuit a for-expression when a Left is encountered. Although the Scaladoc does not explicitly mention for-expression short-circuiting, the code examples suggest to me that it might happen.

Maybe once I spend some time playing with for-expressions the wisdom behind this change will become apparent.

BTW, it is weird that the Scaladoc uses round parentheses and semicolons to delimit for-expression generators, instead of squiggly braces and newlines.

Mike

Alexandru Nedelcu

unread,
Oct 15, 2016, 2:49:05 AM10/15/16
to scala...@googlegroups.com
The idea with the right-biased Either is that Left gets used to signal errors in a majority of cases. And either way, it is almost always a different type. In your example you're using Int for both Left and Right, but then that map wouldn't make sense if Left was a String, right?

This is basically optimizing for the common case. And a nice benefit is that now you can say that Either is a Monad. It also gets rid of forks in the community, since Scalaz has been maintaining a right-biased either, their \/ type, for years and Cats has had Xor, etc. 

--
Alexandru Nedelcu

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

Vlad Patryshev

unread,
Oct 15, 2016, 3:20:25 AM10/15/16
to Simon Ochsenreither, scala-user
My general impression: too much. Not enough conceptual integrity.
Also, is it applicative, by any chance? Does not look like. Sad.

Anyway, I'd rather narrow all this.
And I was always confused, Either is a union, there are no "projections", it's just unnatural. Can't we have, at least try to have, only total functions? Promoting functional programming, etc.

Thanks,
-Vlad

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.

Seth Tisue

unread,
Oct 15, 2016, 11:27:04 AM10/15/16
to scala-user
On Fri, Oct 14, 2016 at 9:07 PM, Michael Slinn <msl...@gmail.com> wrote:
BTW, it is weird that the Scaladoc uses round parentheses and semicolons to delimit for-expression generators, instead of squiggly braces and newlines.

I've noticed that as well. We'd welcome a PR fixing it (at https://github.com/scala/scala/pulls).

Michael Slinn

unread,
Oct 15, 2016, 11:31:53 AM10/15/16
to scala-user, simon.och...@gmail.com
Before the 2.12-RC1 changes, we could say that an Either instances hold the value on the right if their value was stored in the right property, and Either instances hold the value on the left if their values were stored in the left property.

If the right and left, rightProjection and leftProjection properties are deprecated then these sentences lose their meaning.

How should one now refer to values on the left and right?

Mike

Michael Slinn

unread,
Oct 15, 2016, 11:36:43 AM10/15/16
to scala-user
What branch should I use, or should I create a new branch?

Michael Slinn

unread,
Oct 15, 2016, 11:41:12 AM10/15/16
to scala-user, simon.och...@gmail.com
Since Scala 2.12-RC1: deprecated the right and left properties; should we now refer to the Right and Left types instead of the right and left properties? This would mean that we now say an Either instance holds it value in the Left, or in the Right, instead of on the left, or on the right.

Michael Slinn

unread,
Oct 15, 2016, 12:13:03 PM10/15/16
to scala-user

Michael Slinn

unread,
Oct 15, 2016, 1:00:37 PM10/15/16
to scala-user
When the Left and Right types differ, the Scala compiler should be smart enough to know how to construct an Either without a type hint on the left of the equals sign. For example, given these case classes:

scala> case class LivingThing(name: String, species: String)
defined class LivingThing

scala> case class NonLivingThing(name: String, description: String)
defined class NonLivingThing


The type hint on the left of the equals sign should be sufficient to construct the Either without the explicit Right; this is verbose:

scala> val thing1: Either[NonLivingThing, LivingThing] = Right(LivingThing("Leatherback Turtle", "Dermochelys coriacea")) thing1: Either[NonLivingThing,LivingThing] = Right(LivingThing(Leatherback Turtle,Dermochelys coriacea))



I suggest that the Scala compiler not throw an error for this code:

 

scala> val thing1: Either[NonLivingThing, LivingThing] = LivingThing("Leatherback Turtle", "Dermochelys coriacea")
<console>:15: error: type mismatch;
 found   : LivingThing
 required: Either[NonLivingThing,LivingThing]
       val thing1: Either[NonLivingThing, LivingThing] = LivingThing("Leatherback Turtle", "Dermochelys coriacea")

Rex Kerr

unread,
Oct 15, 2016, 1:23:53 PM10/15/16
to Michael Slinn, scala-user, Simon Ochsenreither
`right` and `left` are going to be un-deprecated.

Also, how you talk about it has only a moderate amount to do with which methods are provided.  Personally I use neither "in" nor "on".  ("This Either is a Left," for instance.)

Also, keep in mind that Either is just one of many classes in the library.  In general, special-casing the compiler's treatment of various bits of the library is a bad idea--it makes it much harder to change things, much harder to use alternative implementations, and so on.  It needs to not just be kind of nice but effectively transformative (the way special-cased treatment of functions and tuples is).  Fortunately, implicit conversions will already do what you suggest, and you can define your own.

(Adding such a general implicit conversion to the library is generally a bad idea since it makes it hard to be careful if one wants to be.)

  --Rex


On Sat, Oct 15, 2016 at 8:41 AM, Michael Slinn <msl...@gmail.com> wrote:
Since Scala 2.12-RC1: deprecated the right and left properties; should we now refer to the Right and Left types instead of the right and left properties? This would mean that we now say an Either instance holds it value in the Left, or in the Right, instead of on the left, or on the right.

Naftoli Gugenheim

unread,
Oct 16, 2016, 2:04:15 AM10/16/16
to Michael Slinn, scala-user
What's weird about that?



Mike

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

Naftoli Gugenheim

unread,
Oct 16, 2016, 2:10:14 AM10/16/16
to Michael Slinn, scala-user


On Sat, Oct 15, 2016, 1:00 PM Michael Slinn <msl...@gmail.com> wrote:
When the Left and Right types differ, the Scala compiler should be smart enough to know how to construct an Either without a type hint on the left of the equals sign. For example, given these case classes:

scala> case class LivingThing(name: String, species: String)
defined class LivingThing

scala> case class NonLivingThing(name: String, description: String)
defined class NonLivingThing


The type hint on the left of the equals sign should be sufficient to construct the Either without the explicit Right; this is verbose:

scala> val thing1: Either[NonLivingThing, LivingThing] = Right(LivingThing("Leatherback Turtle", "Dermochelys coriacea")) thing1: Either[NonLivingThing,LivingThing] = Right(LivingThing(Leatherback Turtle,Dermochelys coriacea))


It says what it's doing. It's constructing an instance of Right. You're upcasting it to Either. Either is an ordinary family of types that you can write yourself, that just happens to live in the standard library. NonLivingThing is not an Either.



I suggest that the Scala compiler not throw an error for this code:

 

scala> val thing1: Either[NonLivingThing, LivingThing] = LivingThing("Leatherback Turtle", "Dermochelys coriacea")
<console>:15: error: type mismatch;
 found   : LivingThing
 required: Either[NonLivingThing,LivingThing]
       val thing1: Either[NonLivingThing, LivingThing] = LivingThing("Leatherback Turtle", "Dermochelys coriacea")


You can define an implicit conversion, with the usual dangers.

BTW Dotty has union types, which is roughly like an unboxed Either built into the type system, so it allows you to do the equivalent without the verbosity.



--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

Michael Slinn

unread,
Oct 23, 2016, 12:03:30 PM10/23/16
to scala-user, msl...@gmail.com
I found some behavior that is likely to introduce bugs into user programs. I do not know if this behavior is intended.

scala> val a: Either[Int, Int] = Right(1) a: Either[Int,Int] = Right(1) scala> val b: Either[Int, Int] = Left(2) b: Either[Int,Int] = Left(2) scala> val c: Either[Int, Int] = Left(3) c: Either[Int,Int] = Left(3) scala> val d: Either[Int, Int] = Right(4) d: Either[Int,Int] = Right(4)

scala> val r4 = for { x <- a y <- b z <- c w <- d } yield w r4: scala.util.Either[Int,Int] = Left(2)

I interpret the above to mean that the first generator that returns a Left stops for for-expression from looping through any remaining generators, and establishes the values of all following intermediate variables set from generators for the yield expression. Thus b, with value Left(2), causes all remaining generators (c and d) to also return the current value of b.

Am I wrong? If not, then I am concerned that the user is not warned that w may not have the expected value. If so, please enlighten me.

Mike

Luis Ángel Vicente Sánchez

unread,
Oct 23, 2016, 12:08:13 PM10/23/16
to Michael Slinn, scala-user

How is that any different from the behaviour of Option? Or Future? In all cases one of the inhabitants of the type short-cirtuits the for comprehension. If anything, this change address an inconsistency in the standard library.


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.

Lanny Ripple

unread,
Oct 23, 2016, 1:33:47 PM10/23/16
to scala-user, msl...@gmail.com
I always think of the biased Either as a latch.  I can run whatever processing I want but the first Left in the processing "sticks".  This is very useful to register error conditions since once you hit an error (cannot connect to database so never found my record) you can't process a result but knowing when things went pear shaped is very useful.

If you want to unwrap the Either with a result there's .fold allowing you to process either side and get a result and if you know you have an Either[A,A] there's .merge.

A biased Either will simplify things without losing it's Either-ness (thanks to .swap).

Michael Slinn

unread,
Oct 23, 2016, 2:05:55 PM10/23/16
to scala-user, msl...@gmail.com
The difference is that there is only one value for None, however this is not true for Left. I think that at a minimum, the compiler should issue a warning.

Michael Slinn

unread,
Oct 23, 2016, 2:07:34 PM10/23/16
to scala-user, msl...@gmail.com
Yes, Either has a number of combinators that can be used and yield useful results. I am addressing new behavior that I believe is non-intuitive, probably not useful, and is likely to introduce a new class of error.

Joel Neely

unread,
Oct 23, 2016, 5:13:04 PM10/23/16
to Michael Slinn, scala-user
As the neophyte in the room, this seems to me a matter of two interpretations of Either:
  1. as a general "sum type" over two symmetric options, and
  2. as a semantic device for "failure/error with information".
with "intuitive" depending on the assumed interpretation.

Thanks,
-jn-


On Sun, Oct 23, 2016 at 1:07 PM, Michael Slinn <msl...@gmail.com> wrote:
Yes, Either has a number of combinators that can be used and yield useful results. I am addressing new behavior that I believe is non-intuitive, probably not useful, and is likely to introduce a new class of error.

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Beauty of style and harmony and grace and good rhythm depend on simplicity. - Plato

Rex Kerr

unread,
Oct 23, 2016, 7:47:33 PM10/23/16
to Michael Slinn, scala-user
This is exactly how it behaves with Option--the first time you run into something that isn't a Some, you return with that value (None).

That Either returns the first Left instead of the first (they're all identical so it only matters if you count on side-effects happening) None is maybe a tad surprising if one hasn't thought about it before.

It shouldn't be any harder to get than the Option case, though.

  --Rex


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.

Michael Slinn

unread,
Oct 23, 2016, 8:05:57 PM10/23/16
to scala-user, msl...@gmail.com
I cannot think of a use case where this new right-biased behavior is helpful. I would like to learn of a scenario where the code example I showed exhibits desirable behavior. If such a use case was considered when designing this feature, please put it in the docs and also reply here.

I do not believe that it is fair to say "Option works just this way". Option's "left" has only one value: None. Its behavior is obviously useful as a result. I fail to see how a many- or infinite-valued Left provides value as implemented.

I suggest that either the new right-biased behavior be altered in some fashion or the new behavior be rolled back. Possible modifications that come to mind:
  1. Warn the user when short-circuiting might produce unexpected behavior, and provide an annotation to disable the warning
  2. Issue a compiler error when a yield statement or the body of a for-loop references a variable that was not set due to short circuiting

Mike

Rex Kerr

unread,
Oct 23, 2016, 8:17:02 PM10/23/16
to Michael Slinn, scala-user
for {
  file <- existingFileFrom(filename)
  content <- readAll(file)
  data <- parse(content)
  result <- query(data, yourInput)
} yield result

will give you the successful result in a Right, and the first point of failure in a Left.  This is exactly what you want for the bulk of validation tasks: there is a sequential dependent process running, and if something goes wrong you want to know what (and you can't proceed anyway).

If you want instead to try all alternatives, then a for comprehension won't do it (not with this syntax anyway).

  --Rex

--

Michael Slinn

unread,
Oct 23, 2016, 9:04:30 PM10/23/16
to scala-user, msl...@gmail.com
Rex, thanks for the code example :)  That use case makes sense to me.

I think there are two behaviors in play:
  1. A for-loop short-circuits on a value obtained from a generator by terminating all following loops and not executing the body. This works fine for right-biased Either.
  2. In contrast, when a for-comprehension short-circuits it always yields a value, and there is no way to discover in the body of the for-comprehension if a generator short-circuited or not. This might surface unexpected and possibly undesired results.
Here are more code examples, starting with short-circuiting Option:

scala> val a = Some(1)
a: Some[Int] = Some(1)

scala> val b = None
b: None.type = None

scala> val c = None
c: None.type = None

scala> val d = Some(4)
d: Some[Int] = Some(4)

scala> for {

     |   x <- a
     |   y <- b
     |   z <- c
     |   w <- d
     | } println(w)
(nothing is printed, and I am happy; this is consistent with other Scala behavior)

scala> for {

     |   x <- a
     |   y <- b
     |   z <- c
     |   w <- d
     | } yield w

res2: Option[Int] = None
(Again, I am happy)


Lets once again replace the Options by Rights and Lefts:

scala> val a: Either[Int, Int] = Right(1)
a: Either[Int,Int] = Right(1)

scala> val b: Either[Int, Int] = Left(2)
b: Either[Int,Int] = Left(2)

scala> val c: Either[Int, Int] = Left(3)
c: Either[Int,Int] = Left(3)

scala> val d: Either[Int, Int] = Right(4)
d: Either[Int,Int] = Right(4)

scala> for {

     |   x <- a
     |   y <- b
     |   z <- c
     |   w <- d
     | } println(w)

Nothing is printed. I like this for-loop behavior.


scala> for {

     |   x <- a
     |   y <- b
     |   z <- c
     |   w <- d
     | } yield x + y*10 + z*100 + w*1000
res4: scala.util.Either[Int,Int] = Left(2)

This is going to cause many programmers to recoil in surprise and shock. Some bad press about Scala is likely to follow. Again, there is no way for the body of the for-comprehension to 'know' if short-circuiting happened or not, so a value is computed that is likely not what the user wanted.

Mike

PS, slightly off topic: I just noticed that Either, Right and Left have no get method, although they do have a getOrElse method. I know that monads do not require a get method in order to attain monadhood. However, this seems inconsistent with other standard Scala monads. Is Either now supposed to be a full-fledged monad, with the usual complement of methods that other Scala monads typically have, or is it sorta-kinda-not-really-a-monad, like Try?

Michael Slinn

unread,
Oct 23, 2016, 9:15:05 PM10/23/16
to scala-user, msl...@gmail.com
I fear that I failed to make the point I was trying to in my last programming example. With right-biased Either, performing a computation in a for-comprehension is dangerous because variables may have unexpected values when a generator short-circuits, and there is no way of detecting this inside the for-comprehension. Once the Left value is returned the situation is easily detectable.

Moral: do not perform any computation in a for-comprehension involving right-biased Either, upon pain of hard-to-find bugs.

I am uneasy about languages that allow you to shoot yourself in the foot, and then offers to reload the gun for you afterwards. C++ is infamous for this reason.

Mike

Rex Kerr

unread,
Oct 23, 2016, 9:21:29 PM10/23/16
to Michael Slinn, scala-user
On Sun, Oct 23, 2016 at 6:04 PM, Michael Slinn <msl...@gmail.com> wrote:

Lets once again replace the Options by Rights and Lefts:

scala> val a: Either[Int, Int] = Right(1)
a: Either[Int,Int] = Right(1)

scala> val b: Either[Int, Int] = Left(2)
b: Either[Int,Int] = Left(2)

scala> val c: Either[Int, Int] = Left(3)
c: Either[Int,Int] = Left(3)

scala> val d: Either[Int, Int] = Right(4)
d: Either[Int,Int] = Right(4)


scala> for {

     |   x <- a
     |   y <- b
     |   z <- c
     |   w <- d
     | } yield x + y*10 + z*100 + w*1000

res4: scala.util.Either[Int,Int] = Left(2)

This is going to cause many programmers to recoil in surprise and shock. Some bad press about Scala is likely to follow. Again, there is no way for the body of the for-comprehension to 'know' if short-circuiting happened or not, so a value is computed that is likely not what the user wanted.

Mike


Sorry, I don't understand the problem.  For comprehensions yield the result, if there is one, or the (first, and only evaluated) error in a dependent chain.

Where is the "not what the user wanted"?  You're trying to generate a Right(something), but the thing didn't work, and it told you where it went wrong: Left(2).

I guess if you're expecting something else this is annoying, but what are you expecting and what is wrong with expecting this instead?

  --Rex
 
PS, slightly off topic: I just noticed that Either, Right and Left have no get method, although they do have a getOrElse method. I know that monads do not require a get method in order to attain monadhood. However, this seems inconsistent with other standard Scala monads. Is Either now supposed to be a full-fledged monad, with the usual complement of methods that other Scala monads typically have, or is it sorta-kinda-not-really-a-monad, like Try?

--

Roland Kuhn

unread,
Oct 24, 2016, 2:29:51 AM10/24/16
to Joel Neely, Michael Slinn, scala-user
23 okt. 2016 kl. 23:12 skrev Joel Neely <joel....@gmail.com>:

As the neophyte in the room, this seems to me a matter of two interpretations of Either:
  1. as a general "sum type" over two symmetric options, and

This has come up in this thread before, but I somehow forgot to reply: Either is not a sum type. It is something that is isomorphic to a sum type if the summands are disjoint, but 

Left[Int,Int](42) != Right[Int,Int](42)

Expecting Either to behave like a sum type will lead to pain and suffering.

Regards,

Roland

  1. as a semantic device for "failure/error with information".
with "intuitive" depending on the assumed interpretation.

Thanks,
-jn-


On Sun, Oct 23, 2016 at 1:07 PM, Michael Slinn <msl...@gmail.com> wrote:
Yes, Either has a number of combinators that can be used and yield useful results. I am addressing new behavior that I believe is non-intuitive, probably not useful, and is likely to introduce a new class of error.

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Beauty of style and harmony and grace and good rhythm depend on simplicity. - Plato

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

Roland Kuhn

unread,
Oct 24, 2016, 2:29:51 AM10/24/16
to Michael Slinn, scala-user
w never gets assigned a value because the definition of w is never evaluated. This is the same as for None, empty List, etc. The flatMap method is entirely free in how many times (if at all) it evaluates its argument, and it would not make sense for the compiler to notify its users about this fact.

Regards,

Roland


Mike

Michael Slinn

unread,
Oct 24, 2016, 3:26:28 AM10/24/16
to scala-user, msl...@gmail.com
The problem I describe only manifests if:
  • A monad which can assume of two types has the identical base type for both scenarios. This is always true for Option, and is true for Either[U, V] when U==V; this is always false for Future and Try.
  • A computation is performed in a yield expression. Performing a computation as a generator, which Rex showed in his code example, always works without any problem.

I am asking if the compiler would issue a warning or error when the above condition is detected. For example, here is how to write Rex's code so it misbehaves:


val result = (for {
  file    <- existingFileFrom(filename)
  content <- readAll(file)
  data    <- parse(content)
} yield query(data, yourInput)).flatten

I'd like the compiler to catch this programming error because data will not have been evaluated when readAll fails.

However, a similar for-loop would not be a problem:

(for {
  file    <- existingFileFrom(filename)
  content <- readAll(file)
  data    <- parse(content)
} query(data, yourInput)

Of course, Rex's code is even better than both of these code examples because it always returns a useful result that does not have to be transformed.

Mike

Luis Ángel Vicente Sánchez

unread,
Oct 24, 2016, 4:11:02 AM10/24/16
to Michael Slinn, scala-user

In your example the compiler might issue an error. As you have an Either[U, Either[V, W]] after the yield, it will depend on the implementation of flatten, i.e if flatten only allows U == V or if it allows U <: V.

I still fail to see why a right biased Either is a problem on any of the cases you mention:

1.- map/flatMap would accept a function that would only be applied if the Either contains a Right[V]. Why the fact that U == V, would cause an issue here?

2.- The computation on the yield would never be executed if any of expressions in the for produces a Left. If that's the case, then it will return that Left.

Having a Left is the early termination condition that short-circuits the for-comprehension, making it to return that Left. If everything returns a Right, the for comprehension will return the content of yield inside Right.

I don't find that behaviour neither more confusing of that of an Option, Future or Try, nor deserving to be treated as a special case by the compiler. I'm sure that there would be useful warning messages explaining why the projections have been deprecated. Wouldn't that be enough?

To be honest, I always found the previous Either, with projections, confusing and verbose. So much, that I always bring a 3rd party library like Scalaz or Cats to my projects to have a right biased Either.


--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+unsubscribe@googlegroups.com.

Oliver Ruebenacker

unread,
Oct 24, 2016, 10:30:31 AM10/24/16
to Luis Ángel Vicente Sánchez, Michael Slinn, scala-user

     Hello,

  If it is biased, it should not be called Either/Left/Right. Maybe the intention is to have something better described as Result/Success/Failure?

     Best, Oliver
--
Oliver Ruebenacker
Senior Software Engineer, Diabetes Portal, Broad Institute

Kevin Wright

unread,
Oct 24, 2016, 10:39:23 AM10/24/16
to Oliver Ruebenacker, Luis Ángel Vicente Sánchez, Michael Slinn, scala-user
Just because its biased, doesn’t mean that the bias is towards “success”

I could imagine an API to look up a person that returns an Either[Child,Adult], with the two alternatives needing different processing due to e.g. legal requirements.

If 99% of calls to the service are going to be for adults then it makes sense to bias towards that possibility, but this doesn’t automatically mean the same thing as “success”

Either doesn’t mandate where it should be used.  It’s simply a disjoint union of types, biased towards easier usage for one of them.  In practice, and by convention, this is a very good fit for (and very often used for) success/failure scenarios … but nothing about the type demands that this must always be the case.

Kevin Wright
mail / hangouts / msn : kev.lee...@gmail.com
vibe / skype: kev.lee.wright

"My point today is that, if we wish to count lines of code, we should not regard them as "lines produced" but as "lines spent": the current conventional wisdom is so foolish as to book that count on the wrong side of the ledger" ~ Dijkstra

Oliver Ruebenacker

unread,
Oct 24, 2016, 10:48:57 AM10/24/16
to Kevin Wright, Luis Ángel Vicente Sánchez, Michael Slinn, scala-user

     Hello,

  Maybe then call it Usual/Unusual or Major/Minor or something? Left/Right will confuse people if it is biased.

     Best, Oliver

Rex Kerr

unread,
Oct 24, 2016, 12:19:52 PM10/24/16
to Michael Slinn, scala-user
On Mon, Oct 24, 2016 at 12:26 AM, Michael Slinn <msl...@gmail.com> wrote:
The problem I describe only manifests if:
  • A monad which can assume of two types has the identical base type for both scenarios. This is always true for Option, and is true for Either[U, V] when U==V; this is always false for Future and Try.
  • A computation is performed in a yield expression. Performing a computation as a generator, which Rex showed in his code example, always works without any problem.

I am asking if the compiler would issue a warning or error when the above condition is detected. For example, here is how to write Rex's code so it misbehaves:


val result = (for {
  file    <- existingFileFrom(filename)
  content <- readAll(file)
  data    <- parse(content)
} yield query(data, yourInput)).flatten

I'd like the compiler to catch this programming error because data will not have been evaluated when readAll fails.

There's no error there.  The yield statement will only happen if everything succeeds (i.e. is Right), same as if it's Option all the way through.

Also, Either doesn't have a flatten method.  But even if it did, it would have to have the same behavior as flatMap(identity), which would do the correct thing (preserve the earliest error in a Left, otherwise give the success in a Right).

  --Rex

 

Mike Slinn

unread,
Oct 25, 2016, 11:34:19 AM10/25/16
to scala-user

I'll try one more time. If b short-circuits, y becomes a Left and the value of all the remaining generators also assumes the value of y for the remainder of the iteration. There is no way for the code in the body of the yield statement to detect that a short-circuit happened, and all of the values might appear to be valid. However, the value computed by the body of the for-comprehension and returned by the yield statement is erroneous.

yield x + y*10 + z*100 + w*1000

For myself, I'll try to steer clear of this construct. If the Scala compiler is not going to warn users of this potential problem, then perhaps the various Scala linters might add this to their repertoire.

Mike

Ionuț G. Stan

unread,
Oct 25, 2016, 12:57:52 PM10/25/16
to Mike Slinn, scala-user
On 24/10/2016 04:33, Mike Slinn wrote:
> I'll try one more time. If b short-circuits, y becomes a Left and the
> value of all the remaining generators also assumes the value of y for
> the remainder of the iteration. There is no way for the code in the body
> of the yield statement to detect that a short-circuit happened, and all
> of the values might appear to be valid. However, the value computed by
> the body of the for-comprehension and returned by the yield statement is
> erroneous.

"There is no way for the code in the body of the yield statement to
detect that a short-circuit happened [...]" because execution does not
reach the yield body at all if a short-circuit happens. I don't see any
problem with that.

>> *yield x + y*10 + z*100 + w*1000*
>
> For myself, I'll try to steer clear of this construct. If the Scala
> compiler is not going to warn users of this potential problem, then
> perhaps the various Scala linters might add this to their repertoire.
>
> Mike
>
> --
> You received this message because you are subscribed to the Google
> Groups "scala-user" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to scala-user+...@googlegroups.com
> <mailto:scala-user+...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.


--
Ionuț G. Stan | http://igstan.ro | http://bucharestfp.ro

Roland Kuhn

unread,
Oct 25, 2016, 4:58:23 PM10/25/16
to Mike Slinn, scala-user
24 okt. 2016 kl. 03:33 skrev Mike Slinn <msl...@gmail.com>:

I'll try one more time. If b short-circuits, y becomes a Left and the value of all the remaining generators also assumes the value of y for the remainder of the iteration.


No, this is where you err: if b is a Left then y never gets a value at all, and neither do z and w. There is no confusion because the expression in the yield block (just as any expression after the b generator, as for example c and d) will simply not be executed. This is exactly the same as for Option, Try, etc. Please try it out yourself by placing ??? instead of c, d, or the yield block.

There is no way for the code in the body of the yield statement to detect that a short-circuit happened, and all of the values might appear to be valid. However, the value computed by the body of the for-comprehension and returned by the yield statement is erroneous.


Wrong: no value is computed at all!

yield x + y*10 + z*100 + w*1000

For myself, I'll try to steer clear of this construct. If the Scala compiler is not going to warn users of this potential problem, then perhaps the various Scala linters might add this to their repertoire.

Again, there is nothing to warn about: neither is it possible to give a clear definition of when it would warn, nor is it desirable to warn users that flatMap may not call its argument—or in other words, that the generator “short-circuited” (which is a lot less precise).

Regards,

Roland


Mike

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

Rex Kerr

unread,
Oct 25, 2016, 6:49:29 PM10/25/16
to Mike Slinn, scala-user
I think Ionut and Roland have pinpointed a mistake you're making, but just to be absolutely clear,

for {
  x <- xOrA
  y <- yOrB
  z <- zOrC
  w <- wOrD
} yield x + 2*y + 3*z + 4*w

has exactly the same behavior as

xOrA match {
  case Left(a)  => Left(a)
  case Right(x) => yOrB match {
    case Left(b)  => Left(b)
    case Right(y) => zOrC match {
      case Left(c)  => Left(c)
      case Right(z) => wOrD match {
        case Left(d)  => Left(d)
        case Right(w) => Right(x + 2*y + 3*z + 4*w)
      }
    }
  }
}


As the others have said, you only compute the value if everything "succeeds" (i.e. is a Right).  There is no opportunity to get wrong values, because only when all the values exist is the expression evaluated.  And you can tell whether they all existed because it's in a Right in that case and only in that case.

There is no need for additional "warnings"--in fact, the Left value you get when something is wrong is exactly your warning if you need one!

There is nothing unsound about this, and if the behavior is surprising then the solution is to give better examples about how it works and/or find sources of misinformation or misleading examples and try to remove or de-emphasize them.

  --Rex


--

Mike Slinn

unread,
Oct 25, 2016, 6:51:52 PM10/25/16
to Rex Kerr, scala...@googlegroups.com
Rex,

That is most interesting. Thank you for another helpful code example.

Mike

pagoda_5b

unread,
Oct 25, 2016, 6:59:53 PM10/25/16
to scala-user, msl...@gmail.com
I don't know exactly where to start, but my feeling is that you're making some underlying assumptions about the "structure" of monadic behaviour as  deducted from the concrete instances used for comparison with Either, that is: Option, Future, Try.
It seems that the semantic attributed to the operation is creating confusion about the regular mechanics of the flatMap operation itself.

An Option is a type that can be flatMapped in a sequential fashion, where the first operation producing a None stops any further evaluation.
The result of the for-comprehension is not a projection on one of two values of the Option type.
The same happens for Future. The first Failure in the chain inhibits the evaluation of the other operations and the whole result is the Failure itself.
Once again for the Try, which exactly replicates the behaviour of Future, without the asynchronous evaluation.
It's not by chance that the value method on Future[T] returns a Option[Try[T]]

For Future and Try you have a "binary type constructor" with a bound on the type parameter on one side: the Success side is open to any type T, while the Failure side is restricted to subtypes of Throwable.
The for-comprehension stops evaluating its result to the first Failure encountered.

Now to the Either type. We have exactly the same structure as Future or Try, but for the limitation on the type parameter, which is unbound on both sides.
Let's forget about right biasing the flatMap operation. Until now the Either type was treated symmetrically, so that no side was privileged in any operation and both behaved the same. In this scenario you can't decide how to flatMap such type, because there is no privileged side whose computation should be "carried" on, while the other would "short-circuit" the chain.
Every time you need to favour one side, you should do it explicitly by projecting, implying that the projected side was the one that interested you.

The example would be written as

val result = for {
  file    <- existingFileFrom(filename).right
  content <- readAll(file).right
  data    <- parse(content).right
} yield query(data, yourInput)

or equivalently

val result = for {
  file    <- existingFileFrom(filename).left
  content <- readAll(file).left
  data    <- parse(content).left
} yield query(data, yourInput)

just depending on where did the chained operations chose to put its "meaningful result", in a Right or Left.
The operation we're talking about are existingFileFrom, readAll, parse.

The current proposal is to simply forgo any claim of fair treatment of the Either subtypes and decide for one side to be the default favourite for chaining in a flatMap comprenhension.
The chosen side was the Right for historical reasons, but it's pretty arbitrary, you can easily swap the behaviour by using a method that swaps types.

With the new right-biased either your expression is unencumbered by the necessity to define explicitly a right projection, you  simply write

val result = for {
  file    <- existingFileFrom(filename)
  content <- readAll(file)
  data    <- parse(content)
} yield query(data, yourInput)

And expect, as biased name suggests, that your values should be put in a Right result if you want them to proceed on the next step of the for comprehension.

I hope that this attempt at explaining will help

Ivano

pagoda_5b

unread,
Oct 25, 2016, 7:20:53 PM10/25/16
to scala-user, simon.och...@gmail.com
Hello,
I would answer that this interpretation is misleading.
There's no left and right properties holding a value. The Either always holds a value, but its wrapping type is of a different "shape". You could imagine that the value exists and is tagged by its container as being a Right or Left value.
The right and left methods (not properties), gives you a projection of the Either value along one direction, that gives a special meaning for one of the possible "tags".
A right projection favours values tagged as Right, a left projection values tagged as Left. The favoured treatment is that the value can now be mapped, flatMapped and get (throwing an error if the underlying tag was not the projected one).
A projection is like an explicit assumption that you expect a meaningful value being tagged in one specific way, so you have additional operations making use of that assumption.

To help clarify things let's play with a REPL session

Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_45).
Type in expressions for evaluation. Or try :help.

scala> val left: Either[Int, String] = Left(1)
left: Either[Int,String] = Left(1)

scala> val projected = left.right
projected: scala.util.Either.RightProjection[Int,String] = RightProjection(Left(1))

scala> left. <hit tab>
fold   isLeft   isRight   joinLeft   joinRight   left   merge   right   swap

scala> projected. <hit tab>
canEqual   e        exists   flatMap   foreach   getOrElse   map            productElement    productPrefix   toSeq
copy       equals   filter   forall    get       hashCode    productArity   productIterator   toOption        toString

scala> projected.get
java.util.NoSuchElementException: Either.right.value on Left
  at scala.util.Either$RightProjection.get(Either.scala:453)
  ... 32 elided

scala> projected.map(s => s.charAt(2))
res3: Product with Serializable with scala.util.Either[Int,Char] = Left(1)

Bye
Ivano

On Saturday, October 15, 2016 at 5:31:53 PM UTC+2, Michael Slinn wrote:
Before the 2.12-RC1 changes, we could say that an Either instances hold the value on the right if their value was stored in the right property, and Either instances hold the value on the left if their values were stored in the left property.

If the right and left, rightProjection and leftProjection properties are deprecated then these sentences lose their meaning.

How should one now refer to values on the left and right?

Mike

Simon Hafner

unread,
Oct 25, 2016, 8:14:10 PM10/25/16
to scala-user, msl...@gmail.com
Am Montag, 24. Oktober 2016 09:26:28 UTC+2 schrieb Michael Slinn:

I am asking if the compiler would issue a warning or error when the above condition is detected. For example, here is how to write Rex's code so it misbehaves:


val result = (for {
  file    <- existingFileFrom(filename)
  content <- readAll(file)
  data    <- parse(content)
} yield query(data, yourInput)).flatten

I'd like the compiler to catch this programming error because data will not have been evaluated when readAll fails.
The data dependencies are as follows:

query <- data <parse- content <readAll- file <existingFileName- filename
  ^- yourInput

There is no content when readAll fails, and because data depends on content, there is no data. So it's not even possible to evaluate data after readAll failed, because it doesn't exist.
Reply all
Reply to author
Forward
0 new messages