0

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
  }
}
6
  • 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? Commented Apr 9, 2021 at 14:05
  • @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. Commented Apr 9, 2021 at 14:33
  • 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) Commented Apr 9, 2021 at 15:03
  • Your sample seems like it's too complicated. I would opt out for F[_], it will solve your issue way less simpler Commented Apr 9, 2021 at 20:33
  • @user2963757 I don't understand what you're suggesting. Commented Apr 10, 2021 at 17:18

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Second one gives me trait Bar takes type parameters
@GuruStron My bad, let me fix it.
Given that the type parameter is now associated with the class, I don't see how I can call baz using two vals of type Bar which I know are actually instances of the same class without casting both to a specific class (e.g. Foo).
@MichaelMior I don't understand. Did you want to make Foo handle any subtype S of Bar? If so, I can change my answer, but could you edit your question to better explain what you mean?
@OriginalOriginalOriginalVI Thanks for your patience. I edited my answer to try to clarify.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.