我如何根据其类型参数的类型参数编写具有多态 return 类型的函数?
How can I write a function have a polymorphic return type based on the type argument of its type parameter?
我有一些这样的代码:
sealed trait Foo[A] {
def value: A
}
case class StringFoo(value: String) extends Foo[String]
case class IntFoo(value: Int) extends Foo[Int]
我想要一个可以使用给定子类型类型参数的 A
类型的函数。
// Hypothetical invocation
val i: Int = dostuff[IntFoo](param)
val s: String = dostuff[StringFoo](param)
我不知道如何以有效的方式声明 dostuff
。我能想到的最接近的是
def dostuff[B <: Foo[A]](p: Param): A
但这不起作用,因为 A
在该位置未定义。我可以做类似
的事情
def dostuff[A, B <: Foo[A]](p: Param): A
但是我必须像 dostuff[String, StringFoo](param)
那样调用它,这非常难看。
似乎编译器应该具有将 A
移动到 return 类型所需的所有信息,我怎样才能使它工作 ,无论是在标准 scala 中还是在库中。如果这会影响答案,我目前正在使用 scala 2.10。如果可能的话,我愿意接受仅 2.11 的解决方案,但在 2.10
中不可能
您可能知道,如果您有一个类型为 Foo[A]
的参数,那么您只需 A
:
就可以使方法通用
def dostuff[A](p: Foo[A]): A = ???
由于不一定总是这样,我们可以尝试使用隐式参数来表达A
和B
之间的关系。由于我们不能只将一些泛型参数应用于方法调用(泛型参数推断是全有或全无),我们必须将其拆分为 2 次调用。这是一个选项:
case class Stuff[B <: Foo[_]]() {
def get[A](p: Param)(implicit ev: B => Foo[A]): A = ???
}
您可以在 REPL 中查看类型:
:t Stuff[IntFoo].get(new Param) //Int
:t Stuff[StringFoo].get(new Param) //String
同理的另一个选项,但使用匿名 class,是:
def stuff[B <: Foo[_]] = new {
def apply[A](p: Param)(implicit ev: B <:< Foo[A]): A = ???
}
:t stuff[IntFoo](new Param) //Int
在这里,我用apply
代替了get
,所以你可以更自然地应用这个方法。另外,正如您在评论中所建议的,我在这里使用了 <:<
作为证据类型。对于那些希望了解更多关于这种 广义类型约束 的人,您可以阅读更多 here.
您也可以考虑在这里使用抽象类型成员而不是泛型参数。在与泛型类型推断作斗争时,这通常会提供一个优雅的解决方案。您可以阅读有关抽象类型成员及其与泛型的关系的更多信息 here。
另一种选择是使用类型成员:
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)
请注意,这两种解决方案主要解决 Scala 中的句法限制,您不能固定列表的一种类型参数,而让编译器推断另一种。
我有一些这样的代码:
sealed trait Foo[A] {
def value: A
}
case class StringFoo(value: String) extends Foo[String]
case class IntFoo(value: Int) extends Foo[Int]
我想要一个可以使用给定子类型类型参数的 A
类型的函数。
// Hypothetical invocation
val i: Int = dostuff[IntFoo](param)
val s: String = dostuff[StringFoo](param)
我不知道如何以有效的方式声明 dostuff
。我能想到的最接近的是
def dostuff[B <: Foo[A]](p: Param): A
但这不起作用,因为 A
在该位置未定义。我可以做类似
def dostuff[A, B <: Foo[A]](p: Param): A
但是我必须像 dostuff[String, StringFoo](param)
那样调用它,这非常难看。
似乎编译器应该具有将 A
移动到 return 类型所需的所有信息,我怎样才能使它工作 ,无论是在标准 scala 中还是在库中。如果这会影响答案,我目前正在使用 scala 2.10。如果可能的话,我愿意接受仅 2.11 的解决方案,但在 2.10
您可能知道,如果您有一个类型为 Foo[A]
的参数,那么您只需 A
:
def dostuff[A](p: Foo[A]): A = ???
由于不一定总是这样,我们可以尝试使用隐式参数来表达A
和B
之间的关系。由于我们不能只将一些泛型参数应用于方法调用(泛型参数推断是全有或全无),我们必须将其拆分为 2 次调用。这是一个选项:
case class Stuff[B <: Foo[_]]() {
def get[A](p: Param)(implicit ev: B => Foo[A]): A = ???
}
您可以在 REPL 中查看类型:
:t Stuff[IntFoo].get(new Param) //Int
:t Stuff[StringFoo].get(new Param) //String
同理的另一个选项,但使用匿名 class,是:
def stuff[B <: Foo[_]] = new {
def apply[A](p: Param)(implicit ev: B <:< Foo[A]): A = ???
}
:t stuff[IntFoo](new Param) //Int
在这里,我用apply
代替了get
,所以你可以更自然地应用这个方法。另外,正如您在评论中所建议的,我在这里使用了 <:<
作为证据类型。对于那些希望了解更多关于这种 广义类型约束 的人,您可以阅读更多 here.
您也可以考虑在这里使用抽象类型成员而不是泛型参数。在与泛型类型推断作斗争时,这通常会提供一个优雅的解决方案。您可以阅读有关抽象类型成员及其与泛型的关系的更多信息 here。
另一种选择是使用类型成员:
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)
请注意,这两种解决方案主要解决 Scala 中的句法限制,您不能固定列表的一种类型参数,而让编译器推断另一种。