Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For-comprehension on Either.RightProjection with Tuple2 extractor in generator fails to compile #5589

Open
scabug opened this issue Mar 20, 2012 · 7 comments
Labels
Milestone

Comments

@scabug
Copy link

scabug commented Mar 20, 2012

The for-comprehension in the attempt method fails to compile, despite the fact that the nearly-equivalent desugared version works fine.

object Test extends App {
  def attempt(in: Int): Either[Int, (String, Int)] = in match {
    case 0 => Right("fubar", 13)
    
    case n => {
      val recLeft = attempt(math.abs(n) - 1)
      val recRight = attempt(-math.abs(n) + 1)
      
      // compiles!
      recLeft.right flatMap {
        case (strLeft, numLeft) => {
          recRight.right map {
            case (strRight, numRight) =>
              (strLeft + strRight, numLeft + numRight)
          }
        }
      }
      
      // doesn't compile!
      /* for {
        (strLeft, numLeft) <- recLeft.right
        (strRight, numRight) <- recRight.right
      } yield (strLeft + strRight, numLeft + numRight) */
    }
  }
  
  attempt(42)
  println("Nothing happens")
}

I'm guessing it has something to do with filter, since that's one thing that's missing from my desugared version.

@scabug
Copy link
Author

scabug commented Mar 20, 2012

Imported From: https://issues.scala-lang.org/browse/SI-5589?orig=1
Reporter: @djspiewak
Affected Versions: 2.9.1

@scabug
Copy link
Author

scabug commented Mar 20, 2012

@paulp said:
Minimized.

class A {
  // First three compile.
  def f1(x: Either[Int, String])        = x.right map (y => y)
  def f2(x: Either[Int, String])        = for (y <- x.right) yield y
  def f3(x: Either[Int, (String, Int)]) = x.right map { case (y1, y2) => (y1, y2) }
  // Last one fails.
  def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
/**
./a.scala:5: error: constructor cannot be instantiated to expected type;
 found   : (T1, T2)
 required: Either[Nothing,(String, Int)]
  def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
                                               ^
./a.scala:5: error: not found: value y1
  def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
                                                                            ^
./a.scala:5: error: not found: value y2
  def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
                                                                                ^
three errors found
**/
}

@scabug
Copy link
Author

scabug commented Mar 20, 2012

@paulp said:
See also #1336, to which this is related. The unnecessary call to filter which it injects leads to Nothing being inferred for the type parameter to filter, which leads to typing failure.

@scabug
Copy link
Author

scabug commented Mar 20, 2012

@paulp said:
Fixed in c82ecabad6 .

@scabug
Copy link
Author

scabug commented Mar 5, 2013

@retronym said:
Reverted in scala/scala#1893

@Blaisorblade
Copy link

scala/scala#1893 wasn't merged, so I thought this was fixed, but it still fails in 2.12.1 with a different error:

scala> def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
<console>:11: error: value withFilter is not a member of scala.util.Either.RightProjection[Int,(String, Int)]
       def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
                                                                  ^

Ultimately, scalac is still inserting a questionable call to filter. Or rather, to withFilter. Now that gives a different failure because 2.11 redirected withFilter to filter, while 2.12 doesn't anymore, yet
http://www.scala-lang.org/api/current/scala/util/Either$$LeftProjection.html
http://www.scala-lang.org/api/current/scala/util/Either$$RightProjection.html both only support filter (#10286).

However, desugaring this using filter rather than withFilter by hand produces nonsense, since filter on projections returns weird type Option[Either[X, B]] (I've opened #10285 here):

def f5(x: Either[Int, (String, Int)]) = x.right.filter { case (x, y) => true ; case _ => false } map { case (y1, y2) => (y1, y2) }
<console>:11: error: constructor cannot be instantiated to expected type;
 found   : (T1, T2)
 required: scala.util.Either[Nothing,(String, Int)]
       def f5(x: Either[Int, (String, Int)]) = x.right.filter { case (x, y) => true ; case _ => false } map { case (y1, y2) => (y1, y2) }
                                                                                                                   ^

I think that's the same error as the original error (#5589 (comment)). I suspect #10285 applied there too, but haven't checked all the facts.

@blast-hardcheese
Copy link

blast-hardcheese commented Nov 3, 2017

@Blaisorblade Unfortunately this seems to be a difference in how much down the error path the compiler is going. @paulp's minified code:

  def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))

in 2.10.4 produces:

<console>:8: warning: `withFilter' method does not yet exist on scala.util.Either.RightProjection[Int,(String, Int)], using `filter' method instead
         def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
                                                                    ^
<console>:8: error: constructor cannot be instantiated to expected type;
 found   : (T1, T2)
 required: scala.util.Either[Nothing,(String, Int)]
         def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
                                                      ^
[binding errors removed for brevity]

which has a version of your withFilter error, followed by the underlying error in Paul's response. Adding in a no-op withFilter via implicits gets us the error back:

scala> implicit class FilterableEither[E, T](x: Either[E, T]) {
     |     def withFilter(p: Either[E, T] => Boolean): Either[E, T] = x
     | }
defined class FilterableEither

scala>   def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x) yield ((y1, y2))
<console>:14: error: constructor cannot be instantiated to expected type;
 found   : (T1, T2)
 required: scala.util.Either[Int,(String, Int)]
         def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x) yield ((y1, y2))
                                                      ^

Interestingly, if we alter withFilter to only process the success case, @paulp's example compiles normally:

scala> implicit class FilterableRight[E, T](x: Either.RightProjection[E, T]) {
     |     def withFilter(p: T => Boolean): Either.RightProjection[E, T] = x
     | }
defined class FilterableRight

scala>   def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2))
f4: (x: Either[Int,(String, Int)])Serializable with Product with scala.util.Either[Int,(String, Int)]

scala> f4(Right(("Foo", 5)))
res0: Serializable with Product with scala.util.Either[Int,(String, Int)] = Right((Foo,5))

With the right-biased Either in 2.12, we can do the same thing without the RightProjection:

scala> implicit class FilterableEither[E, T](x: Either[E, T]) {
     |     def withFilter(p: T => Boolean): Either[E, T] = x
     | }
defined class FilterableEither

scala>   def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x) yield ((y1, y2))
f4: (x: Either[Int,(String, Int)])scala.util.Either[Int,(String, Int)]

scala> f4(Right(("Foo", 5)))
res0: scala.util.Either[Int,(String, Int)] = Right((Foo,5))

This isn't a great solution, since the following fail:

  • for { (a, b) <- e if a < 5 } yield ...
  • for { (a: Int, b: Long) <- e } yield ...

but should give some insight into the shape of this bug

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

No branches or pull requests

3 participants