在 Scala 中利用通用 return 类型

Leveraging a generic return type in Scala

所以我想使用通用 return 类型并能够在函数中使用该类型的信息。不确定这是否可能,但这是我想要的:

  def getStuff[A](a: MyObj, b: String): Option[A] = {
    // do some stuff
    A match {
      case String => Some(a.getString(b))
      case Integer => Some(a.getInt(b))
      ...
      case _ => None
    }
  }

但是,如您所知,A match 是不可能的。关于如何实现此目标的任何想法?

这是使用类型类的经典案例:

trait StuffGetter[T] { // typeclass
  def get(obj: MyObj, s: String): Option[T]
}  

implicit val stringGetter = new StuffGetter[String] {
   def get(o: MyObj, s: String): Option[String] = ???
}
implicit val intGetter = new StuffGetter[Int] {
   def get(o: MyObj, s: String): Option[Int] = ???
}

def getStuff[A](a: MyObj, b: String)(implicit ev: StuffGetter[A]): Option[A] =
  ev.get(a, b)

val stuff0 = getStuff[String](obj, "Hello")  // calls get on stringGetter
val stuff1 = getStuff[Int](obj, "World") // call get on intGetter
val stuff2 = getStuff[Boolean](obj, "!") // Compile-time error

StuffGetter 特征定义了您要对泛型类型执行的操作,并且该特征的每个 implicit 值都提供了特定类型的实现。 (对于自定义类型,这些通常放在该类型的伴随对象中;编译器会在那里寻找它们)

当调用 getStuff 时,编译器将查找具有匹配类型的 StuffGetterimplicit 实例。如果不存在这样的实例,这将失败,否则它将在 ev 参数中传递。

这样做的好处是“匹配”是在编译时完成的,并且在编译时也会检测到不支持的类型。

从概念上讲,我们可以区分 运行-time 的模式匹配,看起来像这样

def getStuff[A](...) =
  A match {
    ...
  }

编译时的模式匹配 看起来像这样

def getStuff[A](...)(implicit ev: Foo[A]) = {
   ev.bar(...)
}

要理解的关键概念是类型在 运行 时不存在,因为它们在编译后被“擦除”,所以一旦程序 运行 就没有足够的信息来对类型进行模式匹配]宁。然而在编译时,也就是在程序 运行s 之前,类型确实存在并且 Scala 提供了方法来要求编译器通过 implicit/givens 机制对它们进行有效的模式匹配,看起来像 so

// type class requirements for type A 
trait StringConverter[A] {
  def getOptValue(b: String): Option[A]
}

// evidence which types satisfy the type class 
implicit val intStringConverter: StringConverter[Int] = (b: String) => b.toIntOption
implicit val strStringConverter: StringConverter[String] = (b: String) => Some(b)
implicit def anyStringConverter[A]: StringConverter[A] = (b: String) => None

// compile-time pattern matching on type A
 def getStuff[A](b: String)(implicit ev: StringConverter[A]): Option[A] = {
   ev.getOptValue(b)
  }

getStuff[Int]("3")     // : Option[Int] = Some(value = 3)
getStuff[String]("3")  // : Option[String] = Some(value = "3")
getStuff[Double]("3")  // : Option[Double] = None

这种编译时模式匹配称为类型class模式。

了解类型和 classes 之间的区别是 Scala 中的基本概念之一 https://docs.scala-lang.org/tutorials/FAQ/index.html#whats-the-difference-between-types-and-classes 并且深入研究它有助于理解如何编写类型 classes.

使用自定义类型class 类似于 Getter:

trait KeyedGetter[S, K, A]:
  def get(s: S, key: K): Option[A]

case class MyObj(ints: Map[String, Int], strs: Map[String, String])

object MyObj:
  given KeyedGetter[MyObj, String, Int] with
    def get(m: MyObj, k: String) = m.ints.get(k)

  given KeyedGetter[MyObj, String, String] with
    def get(m: MyObj, k: String) = m.strs.get(k)

def getStuff[A](m: MyObj, key: String)(using g: KeyedGetter[MyObj, String, A]): Option[A] =
  g.get(m, key)

使用 class 个标签:

case class MyObj(ints: Map[String, Int], strs: Map[String, String])

import reflect._
def getStuff[A](m: MyObj, key: String)(using ct: ClassTag[A]): Option[A] = (ct match
  case _ if ct == classTag[String] => m.strs.get(key)
  case _ if ct == classTag[Int] => m.ints.get(key)
  case _ => None
).asInstanceOf[Option[A]]

如果擦除的类型不足,对于类型标签的类似方法,请参见this answer(并忽略其余部分)。