简体   繁体   中英

How can I write a function have a polymorphic return type based on the type argument of its type parameter?

I have some code like this:

sealed trait Foo[A] {
  def value: A
}
case class StringFoo(value: String) extends Foo[String]
case class IntFoo(value: Int) extends Foo[Int]

I'd like to have a function which can use the A type given a subtype's type parameter.

// Hypothetical invocation
val i: Int = dostuff[IntFoo](param)
val s: String = dostuff[StringFoo](param)

I can't figure out how to declare dostuff in a way that works. The closest thing I can figure out is

def dostuff[B <: Foo[A]](p: Param): A

But that doesn't work because A is undefined in that position. I can do something like

def dostuff[A, B <: Foo[A]](p: Param): A

But then I have to invoke it like dostuff[String, StringFoo](param) which is pretty ugly.

It seems like the compiler should have all the information it needs to move A across to the return type, how can I make this work , either in standard scala or with a library. I'm on scala 2.10 currently if that impacts the answer. I'm open to a 2.11-only solution if it's possible there but impossible in 2.10

Another option is to use type members:

sealed trait Foo {
  type Value
  def value: Value
}

case class StringFoo(value: String) extends Foo { type Value = String }
case class IntFoo(value: Int) extends Foo { type Value = Int }

def dostuff[B <: Foo](p: Any): B#Value = ???

// Hypothetical invocation
val i: Int = dostuff[IntFoo](param)
val s: String = dostuff[StringFoo](param)

Note that both solutions mainly work around the syntactic restriction in Scala, that you cannot fix one type parameter of a list and have the compiler infer the other.

As you might know, if you have a parameter of type Foo[A] , then you can make the method generic in just A :

def dostuff[A](p: Foo[A]): A = ???

Since that might not always be the case, we can try to use an implicit parameter that can express the relationship between A and B . Since we can't only apply some of the generic parameters to a method call (generic parameter inference is all or nothing), we have to split this into 2 calls. This is an option:

case class Stuff[B <: Foo[_]]() {
    def get[A](p: Param)(implicit ev: B => Foo[A]): A = ???
}

You can check the types in the REPL:

:t Stuff[IntFoo].get(new Param) //Int
:t Stuff[StringFoo].get(new Param) //String

Another option along the same lines, but using an anonymous class, is:

def stuff[B <: Foo[_]] = new { 
    def apply[A](p: Param)(implicit ev: B <:< Foo[A]): A = ??? 
}

:t stuff[IntFoo](new Param) //Int

Here, I've used apply in stead of get , so you can apply the method more naturally. Also, as suggested in your comment, here I've used <:< in the evidence type. For those looking to learn more about this type of generalized type constraint , you can read more here .

You might also consider using abstract type members instead of generic parameters here. When struggling with generic type inference, this often provides an elegant solution. You can read more about abstract type members and their relationship to generics here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM