测试 Scala 类型类中的成员资格
Posted
技术标签:
【中文标题】测试 Scala 类型类中的成员资格【英文标题】:test for membership in Scala type class 【发布时间】:2021-11-27 22:04:13 【问题描述】:有没有办法测试一个类型是否是一个类型类的成员?例如:
trait Foo[A]
trait Marshaller[Node]
def isFoo(n: Node): Boolean
class MyMarshaller[Node] extends Marshaller[Node]
override def isFoo(n: Node): Boolean = ???
显然有成员存在时执行代码的解决方案:
def isFoo(a: A)(implicit ev: Foo[A]) = // do something Foo-like with `a`
在我的用例中,我重写了 isFoo
方法,因此我也无法更改其签名。
现实世界的问题
Sangria 是一个用于在 Scala 中创建 GraphQL 服务的库。它以有效类型类InputUnmarshaller[Node]
的形式将a marshalling subsystem 编织到其中。在代码中可以看到由上下文限定的类型参数:In: InputUnmarshaller
。
这个概念是消耗输入值并生成输出数据集a Value production,其中的每个元素都需要编组。 Node
类型可以限制为,例如,io.circe.Json
值,如果一个是 using Circe for the marshalling。
还有a Scala marshaller,在那个it only handles Map
types as being map-like 中相当愚蠢。目标是扩展它以支持案例类,例如,通过 Shapeless 和类似地图的类型类。
【问题讨论】:
def isFoo[A](implicit ev: Foo[A] = null): Boolean = Option(ev).isDefined
?虽然,这样做的目的是什么? typeclass 应该用于在编译时验证某些内容,而不是在运行时。
在 Scala 3 中,您还可以使用 NotGiven
来反转逻辑并在不存在类型类的情况下使用方法。
blog.rockthejvm.com/anti-implicits
@Mike LuisMiguelMejíaSuárez 的isFoo
是否有可能因为额外的类型参数A
而为您生成“方法'isFoo' 不会覆盖任何内容”?您的def isFoo
是否在trait Foo[A]
中声明?从你的问题看不清楚。 def isFoo(a: A): Boolean
中的A
来自哪里?来自trait Foo[A]
?那么去掉Luis的isFoo
中的[A]
就够了:def isFoo(implicit ev: Foo[A] = null): Boolean = Option(ev).isDefined
。
@GaëlJ 我猜在 Scala 3 中,在这种情况下,隐式模式匹配会比 NotGiven
更方便。请参阅inline def setFor[T]: Set[T] = summonFrom case given Ordering[T] => new TreeSet[T]; case _ => new HashSet[T]
docs.scala-lang.org/scala3/reference/metaprogramming/… 处的示例
【参考方案1】:
(1)尝试引入类型类IsFoo
trait Foo[A]
trait IsFoo[A]
def value(): Boolean
trait LowPriorityIsFoo
implicit def noFoo[A]: IsFoo[A] = () => false
object IsFoo extends LowPriorityIsFoo
implicit def existsFoo[A](implicit foo: Foo[A]): IsFoo[A] = () => true
def isFoo[A](implicit isFooInst: IsFoo[A]): Boolean = isFooInst.value()
测试:
implicit val intFoo: Foo[Int] = null
isFoo[Int] // true
isFoo[String] // false
实际上,我猜我的 isFoo
只是 @LuisMiguelMejíaSuárez 的一个更复杂的变体
def isFoo[A](implicit ev: Foo[A] = null): Boolean = Option(ev).isDefined
(2) 您想通过继承/子类型多态性来定义Marshaller
的行为。在 JVM 语言中,它是动态调度的(最近,在运行时)。现在您想将它与静态分派的隐式/类型类 (Foo
)/临时多态性(早期,在编译时)混合。您必须使用一些运行时工具,例如运行时反射(将有关 Node
的编译时信息保存到运行时,使用 TypeTag
s)、运行时编译。
import scala.reflect.runtime.universe.TypeTag, typeOf, Quasiquote
import scala.reflect.runtime.currentMirror
import scala.tools.reflect.ToolBox
val tb = currentMirror.mkToolBox()
trait Foo[A]
trait Marshaller[Node]
def isFoo(n: Node): Boolean
class MyMarshaller[Node: TypeTag] extends Marshaller[Node]
// override def isFoo(n: Node): Boolean =
// tb.inferImplicitValue(
// tb.typecheck(tq"Foo[$typeOf[Node]]", mode = tb.TYPEmode
// ).tpe).nonEmpty
override def isFoo(n: Node): Boolean =
util.Try(tb.compile(q"implicitly[Foo[$typeOf[Node]]]")).isSuccess
implicit val intFoo: Foo[Int] = null
new MyMarshaller[Int].isFoo(1) //true
new MyMarshaller[String].isFoo("a") //false
Scala resolving Class/Type at runtime + type class constraint
Is there anyway, in Scala, to get the Singleton type of something from the more general type?
Load Dataset from Dynamically generated Case Class
Implicit resolution fail in reflection with ToolBox
In scala 2 or 3, is it possible to debug implicit resolution process in runtime?
(3) 如果您只想检查 Node
是否为 Map[String, _]
,那么只需运行时反射就足够了
import scala.reflect.runtime.universe.TypeTag, typeOf
trait Marshaller[Node]
def isFoo(n: Node): Boolean
class MyMarshaller[Node: TypeTag] extends Marshaller[Node]
override def isFoo(n: Node): Boolean = typeOf[Node] <:< typeOf[Map[String, _]]
new MyMarshaller[Map[String, _]].isFoo(Map()) //true
new MyMarshaller[Int].isFoo(1) //false
另见Typeable
import shapeless.Typeable
trait Marshaller[Node]
def isFoo(n: Node): Boolean
class MyMarshaller[Node] extends Marshaller[Node]
override def isFoo(n: Node): Boolean = Typeable[Map[String, _]].cast(n).isDefined
new MyMarshaller[Map[String, _]].isFoo(Map()) //true
new MyMarshaller[Int].isFoo(1) //false
(4) 在 Scala 3 中,您可以使用 pattern matching by implicits
import scala.compiletime.summonFrom
trait Foo[A]
trait Marshaller[Node]
def isFoo(n: Node): Boolean
class MyMarshaller[Node] extends Marshaller[Node]
override inline def isFoo(n: Node): Boolean = summonFrom
case given Foo[Node] => true
case _ => false
given Foo[Int] with
new MyMarshaller[Int].isFoo(1) //true
new MyMarshaller[String].isFoo("a") //false
(5) 实际上,我想最简单的方法是将隐式参数从方法移动到类
trait Foo[A]
trait IsFoo[A]
def value(): Boolean
trait LowPriorityIsFoo
implicit def noFoo[A]: IsFoo[A] = () => false
object IsFoo extends LowPriorityIsFoo
implicit def existsFoo[A: Foo]: IsFoo[A] = () => true
trait Marshaller[Node]
def isFoo(n: Node): Boolean
class MyMarshaller[Node: IsFoo] extends Marshaller[Node]
// ^^^^^ HERE
override def isFoo(n: Node): Boolean = implicitly[IsFoo[Node]].value()
implicit val intFoo: Foo[Int] = null
new MyMarshaller[Int].isFoo(1) //true
new MyMarshaller[String].isFoo("a") //false
(6) @LuisMiguelMejíaSuárez的默认隐式思想也可以在这种情况下使用
trait Foo[A]
trait Marshaller[Node]
def isFoo(n: Node): Boolean
class MyMarshaller[Node](implicit ev: Foo[Node] = null) extends Marshaller[Node]
override def isFoo(n: Node): Boolean = Option(ev).isDefined
implicit val intFoo: Foo[Int] = new Foo[Int]
new MyMarshaller[Int].isFoo(1) //true
new MyMarshaller[String].isFoo("a") //false
【讨论】:
@Mike 查看更新 @DmytroMitin 我有时想知道你怎么能提供这么多解决方案:) @LuisMiguelMejíaSuárez 最新发现的最佳解决方案 :) @Mike 你应该在你的问题中提供所有必要的细节。 @Mike 请从头开始尝试解决方案。以上是关于测试 Scala 类型类中的成员资格的主要内容,如果未能解决你的问题,请参考以下文章