如何根据类型参数的类型参数编写具有多态返回类型的函数?

时间:2015-04-10 19:46:33

标签: scala polymorphism type-inference

我有一些像这样的代码:

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移动到返回类型所需的所有信息,如何在标准scala或库中使用 。如果这会影响答案,我现在在scala 2.10上。我愿意接受2.11唯一的解决方案,如果它可能存在,但在2.10

中是不可能的

2 个答案:

答案 0 :(得分:7)

另一种选择是使用类型成员:

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中的语法限制,您无法修复列表的一个类型参数并让编译器推断另一个。

答案 1 :(得分:6)

您可能知道,如果您的参数类型为Foo[A],那么您可以仅在A中使该方法具有通用性:

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

由于情况可能并非总是如此,我们可以尝试使用可以表达AB之间关系的隐式参数。因为我们不仅可以将一些通用参数应用于方法调用(泛型参数推断是全部或全部),我们必须将其拆分为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

沿着相同的行,但使用匿名类的另一个选项是:

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的关系的更多信息。