0
votes

I have the following case class implementing a trait with a generic method. The goal is to have each class S which extends the trait implement the method in a way that the method consumes a parameter of type S and returns a value of type S.

case class Foo(x: Int) extends Bar {
  def baz[Foo](v: Foo): Foo = {
    Foo(v.x + x)
  }
}

trait Bar {
  def baz[S <: Bar](v: S): S
}

However, when compiling this, I get the following error that x cannot be found.

error: value x is not a member of type parameter Foo
    Foo(v.x + x)
          ^
one error found

Even stranger to me is that when I change the method to return Foo(3) (thus removing access to x), I get the following error:

error: type mismatch;
 found   : <empty>.Foo
 required: Foo(in method baz)
    Foo(3)
       ^
one error found

I'm not sure what the types <empty>.Foo and Foo(in method baz) are and why both of these aren't just Foo. There's obviously some things about Scala's type system I'm misunderstanding, so help clearing that up would be appreciated.

Edit

I expect to have multiple classes implementing Bar. I want to be able to call baz on vals of type Bar.

Consider in the example below that a and b are obtained from elsewhere and I know based on the logic of my program that they are both instances of the same class (not necessarily Foo). Is there any way to make a legal call to baz in this case? I know I could cast both to Foo, but this happens in a scenario where I don't know what the specific class is. That is, I know both a and b are of type T <: Bar for the same T, but I don't know what T is.

object Foo {
  def main(args: Array[String]): Unit = {
    val a: Bar[_] = Foo(3)
    val b: Bar[_] = Foo(2)
    a.baz(b) // This call is invalid
  }
}
1
By the way, it seems this answer to an earlier question of yours suggested much the same thing as I did, and you accepted it. Did that not work for you? - user
@OriginalOriginalOriginalVI I guess I was failing to see the connection between these two, but I think you're right that they are basically the same thing. Perhaps this should just be closed as a duplicate. - Michael Mior
I'd suggest typeclasses in that case (although they wouldn't work for your example because you're using wildcards there). I'd also recommend unaccepting my answer if it doesn't solve your question (I'm editing it, but someone else may answer until then) - user
Your sample seems like it's too complicated. I would opt out for F[_], it will solve your issue way less simpler - user2963757
@user2963757 I don't understand what you're suggesting. - Michael Mior

1 Answers

3
votes

The type parameter for baz does not work the way you seem to think it does. In the original trait Bar, it's telling you that you can pass in any S that extends Bar and baz will output another S. That means that in Foo, you have to handle not only Foo but also other classes that inherit from Bar.

Furthermore, the type parameter Foo shadows the class Foo. It is equivalent to this:

case class Foo(x: Int) extends Bar {
  def baz[T](v: T): T = {
    Foo(v.x + 1)
  }
}

T is entirely unrelated to Foo and Bar, and so your method is also not satisfying baz's signature, because it should really be T <: Bar.

What I think you want to do is have S by a type parameter given to Bar itself (this is called F-bounded polymorphism):

case class Foo(x: Int) extends Bar[Foo] {
  def baz(v: Foo): Foo = {
    Foo(v.x + 1)
  }
}

trait Bar[S <: Bar[S]] { this: S =>
  def baz(v: S): S
}

Here, the S can basically be used to refer to the type of the class extending Bar, so the compiler knows baz inputs and outputs Foos.

You could later use it like this:

def test[T <: Bar[T]](a: T, b: T) = a.baz(b)

Based on your edit, though, it seems like you want a typeclass. I would suggest changing the definition of Bar to something like this:

trait Bar[T] {
  def baz(t: T): T
}

Then Foo could be defined like this:

case class Foo(x: Int)

An implicit instance of Bar for Foo would be provided separately (perhaps in Bar's companion object):

object Bar {
  implicit val fooBar: Bar[Foo] = new Bar {
    def baz(v: Foo): Foo = Foo(v.x + 1)
  }
  //For convenience
  def apply[T](implicit bar: Bar[T]): bar
}

You could later use it like this:

def test[T : Bar](a: T, b: T) = Baz[T].bar(a, b)

See Advantages of F-bounded polymorphism over typeclass for return-current-type problem for more info.