Scala:如何使 return 类型的函数通用并依赖于运行时参数?
Scala: How to make the return type of function generic and dependent on runtime arguments?
假设我有:
class Animal
class Bird extends Animal
class Dog extends Animal
如何根据提供的参数编写 returns 运行时类型(鸟或狗)的函数。
我正在尝试类似的东西:
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 中可以做这样的事情吗?
这是不可能的。 return 方法的类型必须在编译时已知,即它 return 类型 T
。但是,类型参数T
必须在调用方法时确定,而不是在之后。调用者必须事先知道类型,所以你可以在这里做的最好的事情是 return Animal
.
val newAnimal: ??? = createAnimal(runtimeParam)
// ^ What type goes here?
// The compiler needs to infer it, but it can't that way
因此,当调用 createAnimal
时,您需要等待 Dog
、Bird
或 Animal
。由于您不知道那可能是什么,所以您只能说它是 Animal
。如有必要,您稍后可以使用类型测试来检查返回的内容。
除非您自己填写,否则类型参数不会执行任何操作。否则,你应该有:
def createAnimal(doesItBark: Boolean): Animal = {
if (doesItBark) return new Dog()
else return new Bird()
}
这是否可能在很大程度上取决于您如何定义问题。用自定义类型编写非常相似的东西并不难 class:
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
这一切都归功于路径依赖类型的魔力。
假设我有:
class Animal
class Bird extends Animal
class Dog extends Animal
如何根据提供的参数编写 returns 运行时类型(鸟或狗)的函数。
我正在尝试类似的东西:
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 中可以做这样的事情吗?
这是不可能的。 return 方法的类型必须在编译时已知,即它 return 类型 T
。但是,类型参数T
必须在调用方法时确定,而不是在之后。调用者必须事先知道类型,所以你可以在这里做的最好的事情是 return Animal
.
val newAnimal: ??? = createAnimal(runtimeParam)
// ^ What type goes here?
// The compiler needs to infer it, but it can't that way
因此,当调用 createAnimal
时,您需要等待 Dog
、Bird
或 Animal
。由于您不知道那可能是什么,所以您只能说它是 Animal
。如有必要,您稍后可以使用类型测试来检查返回的内容。
除非您自己填写,否则类型参数不会执行任何操作。否则,你应该有:
def createAnimal(doesItBark: Boolean): Animal = {
if (doesItBark) return new Dog()
else return new Bird()
}
这是否可能在很大程度上取决于您如何定义问题。用自定义类型编写非常相似的东西并不难 class:
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
这一切都归功于路径依赖类型的魔力。