带隐式参数的通用函数

时间:2016-07-19 07:42:54

标签: scala generics

我有一种情况,我正在尝试创建一个泛型函数,该函数应该能够接受在其伴随对象中指定某个隐含值的类的任何实例。我在下面复制了我的问题:

// Mocking up the library I am working with
trait Formatter[A] {
    def output(o: A): String
    def input(s: String): A
}


// Some models
trait Human

case class Child(name: String) extends Human
object Child {
    implicit val f: Formatter[Child] = new Formatter[Child] {
        override def output(c: Child): String = { ... }
        override def input(s: String): Child = { ... }
    }
}

case class Teen(name: String) extends Human
object Teen {
    implicit val f: Formatter[Teen] = new Formatter[Teen] {
        override def output(t: Teen): String = { ... }
        override def input(s: String): Teen = { ... }
    }
}


// The generic function
def gen[A <: Human](a: A)(implicit format: Formatter[A]) = {
    // Do something with a formatter...
}

一切正常,我可以将ChildTeen的实例传递给我的gen函数:

gen(Child("Peter"))
gen(Teen("Emily"))

我遇到的问题是,在运行时我只知道我传递的实例将是Human的子类型:

// Example of unknown subtype
val human: Human = Random.nextBoolean match {
    case true => Child("Peter")
    case false => Teen("Emily")
}

gen(human)  // Error: Could not find implicit value for parameter format...

我理解错误是因为Human没有配套对象,因此没有Formatter的实现。

如何向Human添加一个约束,说“任何扩展Human的东西都会实现一个新的Formatter”?

2 个答案:

答案 0 :(得分:1)

你不需要暗示这一点。 只需让你的子类直接指向实现:

trait Human[+A <: Human] {
 def formatter: Formatter[A]
}

case class Child(name: String) extends Human[Child] {
 def formatter = Child.f
}

// etc
def gen[A <: Human](a: A) {
   // do something with a.formatter 
}

当然,Formatter中的A也必须是协变的。否则,所有的赌注都是关闭的:你根本无法做你想做的事情 - 没有任何有用的gen可以做到这一点而不知道具体的类型。

如果gen中不需要具体类型的具体内容,你仍然可以通过明确地枚举它们来使用implicits(但我真的不明白你为什么会这样想):

 object Human {
    implicit def formatter(h: Human): Formatter[_] = h match {
      case Child(_) => Child.f
      case Teen(_) => Teen.f
    }
 }

 gen(h: Human)(implicit f: Formatter[_]) { ... }

就像我说的那样,这似乎并不是很有用,所以不确定为什么你想要这个超过上述方法。

答案 1 :(得分:1)

您的方案失败,因为您应该实现Formatter [Human]。我认为你想要的是所有的人都应该能够拥有这种格式&#34;能力&#34;代替。

此时你有两个选项,一个是在Human trait中包含一个格式化方法(如果你想让它静态,这个实现可能在对象中)或者尝试使用dsl方法创建一个类为人类提供新能力的责任:&#34;格式&#34;。

第一种方法可能是这样的:

trait Human { def format:String }

case class Child(name: String) extends Human {
  import Child._
  override def format = Child.staticFormat(this)
}

object Child {
  def staticFormat(c: Child): String = s"Child(${c.name})"
}

但我认为&#34;格式&#34;不应该在合同中#34;人类&#34;所以我更喜欢第二种方法:

trait Human

case class Child(name: String) extends Human

case class Teen(name: String) extends Human

import scala.language.implicitConversions

class HumanFormatter(human: Human) {
  def format: String = human match {
    case c: Child => s"Child(${c.name})"
    case t: Teen => s"Teen(${t.name})"
  }
}

object HumanDsl {
  implicit def humanFormatter(human: Human): HumanFormatter = new HumanFormatter(human) 
}

object Test extends App {
  def human: Human = Random.nextBoolean match {
    case true => Child("Peter")
    case false => Teen("Emily")
  }

  import HumanDsl._

  for(i <- 1 to 10) println(human.format)
}

两种解决方案之间有什么区别? 在第一个中,您强制所有新的Human类实现格式方法,以便您可以确保您的代码始终有效。但同时......你正在添加一个方法,从我的观点来看,没有必要,我认为一个案例类应该只有所需的信息,如果你需要格式化/解析那个类,那么更好的是在需要时添加此功能(dsl方法)。

另一方面,使用dsl,您应该在创建新的Human类时随时更新格式化程序。所以这意味着如果创建了一个新的Human类,上面的human.format方法将会失败(您可以始终匹配_来执行默认行为或引发自定义错误。)

我认为这是一个设计问题,我希望这会对你有所帮助。

编辑:

就像注释显示人类特征可以被密封以确保如果没有覆盖某些类,则不会编译HumanFormatter模式匹配。