Scala:如何使返回类型的函数通用并依赖于运行时参数?

时间:2015-06-26 14:55:14

标签: scala reflection polymorphism

说我有:

class Animal
class Bird extends Animal
class Dog extends Animal

如何根据提供的参数编写一个返回运行时类型(Bird或Dog)的函数。

我尝试过类似的事情:

import scala.reflect.ClassTag
def createAnimal[T <: Animal : ClassTag](doesItBark: Boolean): T = {
    if (doesItBark) return new Dog()
    else return new Bird()
}


val azor = createAnimal(doesItBark = true) //azor's type should be Dog

哪个不起作用。

是否可以在Scala中执行类似的操作?

2 个答案:

答案 0 :(得分:4)

这是不可能的。必须在编译时知道方法的返回类型,即它返回类型T。但是,必须在调用方法时确定类型参数T,而不是在之后。调用者必须事先知道类型,所以你可以做的最好的事情就是返回Animal

val newAnimal: ??? = createAnimal(runtimeParam)
           //   ^ What type goes here? 
           //     The compiler needs to infer it, but it can't that way

因此,在致电createAnimal时,您需要DogBirdAnimal。既然你不知道那可能是什么,你只能说它是Animal。如果有必要,您可以稍后使用类型测试来检查您返回的内容。

除非您自己填写,否则类型参数不会做任何事情。否则,你应该只有:

def createAnimal(doesItBark: Boolean): Animal = {
    if (doesItBark) return new Dog()
    else return new Bird()
}

答案 1 :(得分:4)

这是否可能取决于您如何定义问题。编写与自定义类型类似的东西并不太难:

class Animal
class Bird extends Animal
class Dog extends Animal

sealed trait ChooseAnimal[A <: Animal] { def createAnimal: A }

val isBarker: ChooseAnimal[Dog] = new ChooseAnimal[Dog] {
  def createAnimal: Dog = new Dog
}

val isNotBarker: ChooseAnimal[Bird] = new ChooseAnimal[Bird] {
  def createAnimal: Bird = new Bird
}

def createAnimal[A <: Animal](choose: ChooseAnimal[A]): A =
  choose.createAnimal

然后:

scala> createAnimal(isBarker)
res0: Dog = Dog@25cd1055

scala> createAnimal(isNotBarker)
res1: Bird = Bird@1f4e89bc

请注意适当的静态类型。这并不是你要求的,但它非常相似。如果您确实想使用Boolean作为选择器,则需要Shapeless之类的内容:

import shapeless._

trait ChooseBarker[B <: Boolean, A <: Animal] { def createAnimal: A }

implicit val barker: ChooseBarker[Witness.`true`.T, Dog] =
  new ChooseBarker[Witness.`true`.T, Dog] { def createAnimal: Dog = new Dog }

implicit val nonBarker: ChooseBarker[Witness.`false`.T, Bird] =
  new ChooseBarker[Witness.`false`.T, Bird] { def createAnimal: Bird = new Bird }

def createAnimal[B <: Boolean, A <: Animal](w: Witness.Aux[B])(implicit
  choose: ChooseBarker[B, A]
): A = choose.createAnimal

然后:

scala> createAnimal(true)
res0: Dog = Dog@46c09af6

scala> createAnimal(false)
res1: Bird = Bird@186b9fc2

我们再次获得正确的静态类型。

这种方法仍有一些局限性 - 例如。如果论证不是字面意思,那么你必须确定你已经有了Witness个实例 - 但它已经足够接近你所说的了。要求我不要认为我们应该说这种事情是不可能的。

值得注意的是,如果你真的想要,你甚至可以不使用类型参数:

import shapeless._

sealed trait ChooseBarker[B] extends DepFn0 { type Out <: Animal }

object ChooseBarker {
  type Aux[B, A <: Animal] = ChooseBarker[B] {
    type Out = A
  }

  def mk[B, A <: Animal](a: A): Aux[B, A] = new ChooseBarker[B] {
    type Out = A

    def apply(): A = a
  }

  implicit val barker: Aux[Witness.`true`.T, Dog] = mk(new Dog)
  implicit val nonBarker: Aux[Witness.`false`.T, Bird] = mk(new Bird)
}

def createAnimal(w: Witness)(implicit choose: ChooseBarker[w.T]): choose.Out =
  choose()

然后:

scala> val dog: Dog = createAnimal(true)
dog: Dog = Dog@1681d515

scala> val bird: Bird = createAnimal(false)
bird: Bird = Bird@76634045

这一切都归功于路径依赖类型的魔力。