测试 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árezisFoo 是否有可能因为额外的类型参数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 的编译时信息保存到运行时,使用 TypeTags)、运行时编译。

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 类型类中的成员资格的主要内容,如果未能解决你的问题,请参考以下文章

我可以解压缩变量以检查其他列表中的成员资格吗?

scala中隐式转换之总结

继承类中的c ++成员变量类型覆盖

python 中的成员资格测试比 set() 更快

熊猫数据框列中的成员资格测试

JavaSE8基础 类中的public成员方法 可以返回 private static类型的成员变量