简体   繁体   English

具有隐式参数的泛型函数

[英]Generic function with implicit parameter

I have a situation where I am trying to create a generic function which should be able to take any instance of a class which specifies a certain implicit value in its companion object. 我遇到一种情况,我试图创建一个通用函数,该函数应该能够接受在其伴随对象中指定某个隐式值的类的任何实例。 I have replicated my problem below: 我在下面复制了我的问题:

// 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...
}

This all works fine, I can pass an instance of a Child or a Teen to my gen function: 一切正常,我可以将ChildTeen的实例传递给我的gen函数:

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

What I am having trouble with is that at run time I only know that the instance I am passing will be a subtype of a Human : 我遇到的麻烦是,在运行时我只知道我传递的实例将是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...

I understand that the error is because Human has no companion object and therefore it has no implementation of a Formatter . 我知道该错误是因为Human没有伴侣对象,因此它没有Formatter实现。

How can I add a constraint to Human that says "anything extending Human will implement a new Formatter" ? 我如何向Human添加一个约束,说“任何扩展Human的东西都将实现新的Formatter”?

You don't need implicits for this. 您不需要为此的隐式。 Just make your subclasses point to the implementation directly: 只需使您的子类直接指向实现即可:

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 
}

Of course, Formatter needs to be covariant in A too. 当然, Formatter需要在A是协变A Otherwise, all bets are off: you simply cannot do what you want - there is nothing useful gen could do with it without knowing the specific type anyway. 否则,所有的赌注都落空了:您根本无法做您想做的事-在不知道具体类型的情况下,有用的gen不能做任何事。

If specifics of the concrete type are not needed in gen, you can still use implicits by enumerating them explicitly like this (but I don't really see why you would want that): 如果gen中不需要具体类型的细节,您仍然可以通过这样隐式枚举隐式来使用隐式(但是我真的不明白为什么要这么做):

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

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

Like I said, this does not seem very useful though, so not sure why you want want this over the above approach. 就像我说的那样,这似乎并不是很有用,所以不确定在上述方法中为什么要这么做。

Your scenario fails because you should implement a Formatter[Human]. 您的方案失败了,因为您应该实现Formatter [Human]。 I think that what you want is that all the Human should be able to have this format "capability" instead. 我认为您想要的是所有人都应该能够拥有这种“能力”格式。

At this point you have two options, one is to include in the Human trait a method for formatting (this implementation could be in the object if you want it static) or try a dsl approach where you will create a class with the responsibility to provide humans a new capability: "format". 此时,您有两种选择,一种是在Human trait中包括一种格式化方法(如果您希望将其实现为静态,则此实现可以在对象中),或者尝试使用dsl方法,其中您将创建一个负责提供人类的一项新功能:“格式”。

The first approach could be something like this: 第一种方法可能是这样的:

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})"
}

However I think "format" shouldn't be in the contract "Human" so I prefer the second approach: 但是我认为“格式”不应包含在“人”的合同中,因此我更喜欢第二种方法:

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)
}

What are the differences between both solutions? 两种解决方案之间有什么区别? In the first one you force all the new Human classes to implement a format method so you can assure that your code will work always. 在第一个示例中,您将强制所有新的Human类实现一种format方法,以便确保您的代码将始终有效。 But at the same time... you are adding a Human a method that from my point of view is not necessary, I think a case class should have only the information needed and if you need to format/parse that class then is better to add this functionality just when needed (dsl approach). 但同时...您正在向Human添加一个方法,从我的角度来看,该方法不是必需的,我认为案例类应该只包含所需的信息,如果您需要格式化/解析该类,则最好仅在需要时添加此功能(dsl方法)。

In the other hand, with dsl you should update the formatter anytime a new Human class is created. 另一方面,使用dsl时,您应在创建新的Human类时更新格式化程序。 So it means that the human.format method above will fail if a new Human class is created (you can always match _ to do a default behaviour or raise a custom error). 因此,这意味着如果创建了新的Human类,则上面的human.format方法将失败(您可以始终匹配_来执行默认行为或引发自定义错误)。

I think is a matter of design, I hope this would help you a little bit. 我认为这是设计问题,希望这对您有所帮助。


Edited: 编辑:

just like a comment showed the Human trait could be sealed to ensure that the HumanFormatter pattern match doesn't compile if some class is not covered. 就像一条评论显示,如果不覆盖某些类,可以密封Human特性以确保不会编译HumanFormatter模式匹配。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM