测试Scala类型类的成员



是否有一种方法来测试类型是否是类型类的成员?例如:

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]。在代码中,可以看到由上下文限定的类型参数:In: InputUnmarshaller.

这个概念是一个消耗输入值和生成的输出数据集作为生产价值,其中的每个元素需要打包。如果使用Circe进行封送,则可以将Node类型限制为io.circe.Json值。

还有一个Scala编组器,它非常愚蠢,因为它只处理Map类型,就像map一样。目标是扩展它以支持case类,例如,通过Shapeless和一个类似map的类型类。

(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árezdef 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在运行时解析类/类型+类型类约束

无论如何,在Scala中,从更一般的类型中获得单例类型的东西吗?

从动态生成的Case类中加载数据集

使用ToolBox

反射时隐式解析失败在scala 2或3中,是否可以在运行时调试隐式解析过程?


(3)如果你只是想检查NodeMap[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中,你可以通过隐式

使用模式匹配
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)@LuisMiguelMejiaSuarez默认隐式的想法也可以在这种情况下使用

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

相关内容

  • 没有找到相关文章

最新更新