Skip to content

Scala 2 regression: F-bounds + wildcard #13133

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

Closed
japgolly opened this issue Jul 23, 2021 · 9 comments
Closed

Scala 2 regression: F-bounds + wildcard #13133

japgolly opened this issue Jul 23, 2021 · 9 comments

Comments

@japgolly
Copy link
Contributor

Compiler version

  • 3.0.0
  • 3.0.1
  • 3.0.2-RC1

Minimized code

object Blah {
  trait DomZipper[F[_], X, A, Self[G[_], B] <: DomZipper[G, X, B, Self]]
  def apply[Fast[f[_], a] <: DomZipper[f, _, a, Fast]] = () // error
}

object Blah1 {
  trait DomZipper[X, A, Self[B] <: DomZipper[X, B, Self]]
  def apply[Fast[a] <: DomZipper[_, a, Fast]] = () // error
}

object Blah2 {
  trait DomZipper[F[_], X, Self[G[_]] <: DomZipper[G, X, Self]]
  def apply[Fast[f[_]] <: DomZipper[f, _, Fast]] = () // error
  
  def apply1[Fast[f[_]] <: DomZipper[f, _, Fast]] = () // error
}

object Blah3 {
  trait DomZipper[F[_], Self[G[_]] <: DomZipper[G, Self]]
  def apply[Fast[f[_]] <: DomZipper[f, Fast]] = () // ok
}

object Blah4 {
  trait DomZipper[Self <: DomZipper[Self]]
  def apply[Fast <: DomZipper[Fast]] = () // ok
}

object Blah5 {
  trait DomZipper[F[_], A, Self[G[_], B] <: DomZipper[G, B, Self]]
  def apply[Fast[f[_], a] <: DomZipper[f, a, Fast]] = () // ok
}

object Blah6 {
  trait DomZipper[F[_], X, A, Self[G[_], B] <: DomZipper[G, X, B, Self]]
  def apply[Fast[f[_], a] <: DomZipper[f, X, a, Fast], X] = () // ok
}

Output

[error] -- [E057] Type Mismatch Error: a.scala:3:48 
[error] 3 |  def apply[Fast[f[_], a] <: DomZipper[f, _, a, Fast]] = () // error
[error]   |                                                ^
[error]   |Type argument Fast does not conform to upper bound [G[_$2], B] =>> Blah.DomZipper[G, Blah.DomZipper[f, ?, a, Fast]#X, B, Fast]
[error] -- [E057] Type Mismatch Error: a.scala:8:39 
[error] 8 |  def apply[Fast[a] <: DomZipper[_, a, Fast]] = () // error
[error]   |                                       ^
[error]   |Type argument Fast does not conform to upper bound [B] =>> Blah1.DomZipper[Blah1.DomZipper[?, a, Fast]#X, B, Fast]
[error] -- [E057] Type Mismatch Error: a.scala:13:42 
[error] 13 |  def apply[Fast[f[_]] <: DomZipper[f, _, Fast]] = () // error
[error]    |                                          ^
[error]    |Type argument Fast does not conform to upper bound [G[_$5]] =>> Blah2.DomZipper[G, Blah2.DomZipper[f, ?, Fast]#X, Fast]
[error] -- [E057] Type Mismatch Error: a.scala:15:43 
[error] 15 |  def apply1[Fast[f[_]] <: DomZipper[f, _, Fast]] = () // error
[error]    |                                           ^
[error]    |Type argument Fast does not conform to upper bound [G[_$5]] =>> Blah2.DomZipper[G, Blah2.DomZipper[f, ?, Fast]#X, Fast]
[error] four errors found

Expectation

Compiles with Scala 2.12 and 2.13.

@smarter
Copy link
Member

smarter commented Jul 23, 2021

Not going to fix all the f-bounds issues sorry :).

@smarter smarter removed their assignment Jul 23, 2021
@japgolly
Copy link
Contributor Author

Umm ok.... Why?

@smarter
Copy link
Member

smarter commented Jul 24, 2021

Sorry if it wasn't clear I was reacting to:

Kordyjan assigned smarter yesterday

To explain why I un-assigned myself. But maybe someone else will be brave enough to try to fix this!

@odersky
Copy link
Contributor

odersky commented Jul 24, 2021

I think this needs further minimization. F-bounds are a pain, and wildcards are a pain, together they are a royal pain. No idea whether this is supposed to compile or not. Scala 2.12/2.13 can't provide a yardstick here since it's approach is sufficiently different.

In other conversations I recommend to eliminate F-bounds entirely and use intersections instead. I think this is the best way forward, and wish we could retire F-bounds from Scala for good.

@odersky odersky added the stat:needs minimization Needs a self contained minimization label Jul 24, 2021
@Katrix
Copy link
Contributor

Katrix commented Jul 24, 2021

Removing F-bounds will make interop with some libraries a lot harder, which is not something I like the idea of.

@japgolly
Copy link
Contributor Author

@smarter Oh, thanks for clarifying! I was so confused and none of the explanations I could come up with in my head made any sense to me haha.

@odersky Yeah F-bounds aren't fun and I don't like to use them very often. The only time I reach for them is have a hierarchy of types and I want to have a method in the super type that allows users to change a type-parameter without losing the subtype identity. Did I explain that correctly? So like if you wanted to have Animal[Food] with a def useFood[F]: Animal[F] method, and then in a Cat[F] extends Animal[F] subtype you want the useFood method to return a Cat[F] instead of an Animal[F]. Without every single subtype overriding the type signature, how can this be archived without F-bound types?

I think this needs further minimization

It's currently minimised to two lines. I could add a semicolon to make it one line if you like hehe. Jokes aside, the snippet looks big only because I've added a bunch of variants. This is probably the best minimisation:

  trait DomZipper[X, A, Self[B] <: DomZipper[X, B, Self]]
  def apply[Fast[a] <: DomZipper[_, a, Fast]] = () // error

@japgolly
Copy link
Contributor Author

@odersky I just read through the other conversation you linked where you describe how F-bounds can be replaced with intersections. Very cool idea, I tried it out with Scala 2 and it works fine!

The pet example for Scala 2:

object OkCool {
  trait Pet[A] { this: A =>
    def name: String
    def renamed(newName: String): A with Pet[A]
  }

  case class Fish(name: String, age: Int) extends Pet[Fish] {
    def renamed(newName: String) = copy(name = newName)
  }

  def esquire[A](a: Pet[A]): Pet[A] = a.renamed(a.name + ", Esq.")

  val a = Fish("Jimmy", 2)
  val b = a.renamed("Bob")
  val c = esquire(b)
}

And I tried with a type parameter on the super type:

object Poly {
  trait Pet[F, Self[_]] { this: Self[F] =>
    def food: F
    def withFood[G](g: G): Pet[G, Self] with Self[G]
  }

  case class Fish[F](food: F) extends Pet[F, Fish] {
    def withFood[G](g: G): Fish[G] = Fish(g)
    def fishFood = food
  }

  def eatStrings[F, S[_]](p: Pet[F, S]): Pet[String, S] with S[String] =
    p.withFood(123).withFood("")

  val f = Fish(1)
  val g = eatStrings(f)
  g.fishFood: String
}

Very cool 👍

@odersky
Copy link
Contributor

odersky commented Jul 25, 2021

It's currently minimised to two lines. I could add a semicolon to make it one line if you like hehe.

Ah, that's reassuring! I just glanced at the code but was not aware that it exercised multiple independent failures. I'll take a look then but it will have to wait a bit.

Removing F-bounds will make interop with some libraries a lot harder, which is not something I like the idea of.

Yes, I agree. It's one of the things we inherited from Scala that otherwise we would be better without. Here's a shortlist of awkward Java features we still have to support.

  • F-bounds
  • capture conversion for wildcards
  • unchecked nulls
  • covariant arrays
  • serialization

That's what I can think of right now - there are probably others as well.

@odersky odersky self-assigned this Jul 25, 2021
@odersky odersky added stat:revisit and removed stat:needs minimization Needs a self contained minimization labels Aug 1, 2021
@odersky
Copy link
Contributor

odersky commented Aug 1, 2021

It's a missing capture conversion. The error message is

trait DomZipper[X, A, Self[B] <: DomZipper[X, B, Self]]
def apply[Fast[a] <: DomZipper[_, a, Fast]] = () // error
                                     ^
  Type argument Fast does not conform to upper bound [B] =>> DomZipper[DomZipper[?, a, Fast]#X, B, Fast]

Essentially it needs to argue that for each version of Fast there is one specific first argument of X. I am not even sure this is true. Scala 2 handles these wildcards differently and in ways where we know that there are soundness problems. The example is too complex for me to figure out whether this should be sound or not. So I don't think I can help here. If someone else wants to step in, please re-open as needed.

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

No branches or pull requests

5 participants