在 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
时,编译器将查找具有匹配类型的 StuffGetter
的 implicit
实例。如果不存在这样的实例,这将失败,否则它将在 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(并忽略其余部分)。
所以我想使用通用 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
时,编译器将查找具有匹配类型的 StuffGetter
的 implicit
实例。如果不存在这样的实例,这将失败,否则它将在 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(并忽略其余部分)。