簡體   English   中英

具有隱式參數的泛型函數

[英]Generic function with implicit parameter

我遇到一種情況,我試圖創建一個通用函數,該函數應該能夠接受在其伴隨對象中指定某個隱式值的類的任何實例。 我在下面復制了我的問題:

// 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”?

您不需要為此的隱式。 只需使您的子類直接指向實現即可:

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是協變A 否則,所有的賭注都落空了:您根本無法做您想做的事-在不知道具體類型的情況下,有用的gen不能做任何事。

如果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[_]) { ... }

就像我說的那樣,這似乎並不是很有用,所以不確定在上述方法中為什么要這么做。

您的方案失敗了,因為您應該實現Formatter [Human]。 我認為您想要的是所有人都應該能夠擁有這種“能力”格式。

此時,您有兩種選擇,一種是在Human trait中包括一種格式化方法(如果您希望將其實現為靜態,則此實現可以在對象中),或者嘗試使用dsl方法,其中您將創建一個負責提供人類的一項新功能:“格式”。

第一種方法可能是這樣的:

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

但是我認為“格式”不應包含在“人”的合同中,因此我更喜歡第二種方法:

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類實現一種format方法,以便確保您的代碼將始終有效。 但同時...您正在向Human添加一個方法,從我的角度來看,該方法不是必需的,我認為案例類應該只包含所需的信息,如果您需要格式化/解析該類,則最好僅在需要時添加此功能(dsl方法)。

另一方面,使用dsl時,您應在創建新的Human類時更新格式化程序。 因此,這意味着如果創建了新的Human類,則上面的human.format方法將失敗(您可以始終匹配_來執行默認行為或引發自定義錯誤)。

我認為這是設計問題,希望這對您有所幫助。


編輯:

就像一條評論顯示,如果不覆蓋某些類,可以密封Human特性以確保不會編譯HumanFormatter模式匹配。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM