测试 Scala 类型 class 中的成员资格
test for membership in Scala type class
有没有办法测试一个类型是否是一个类型class的成员?例如:
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 is a library for creating GraphQL services in Scala. It has a marshalling subsystem 以有效类型 class、InputUnmarshaller[Node]
的形式交织其中。在代码中可以看到由上下文限定的类型参数:In: InputUnmarshaller
.
概念是消耗输入值并生成输出数据集 a Value production, each element of which needs to be marshalled. The Node
type can be restricted to, for example, io.circe.Json
values, if one is using Circe for the marshalling。
还有a Scala marshaller, which is quite dumb in that it only handles Map
types as being map-like。目标是扩展它以支持案例 classes,例如,通过 Shapeless 和类似地图的类型 class.
(1) 尝试引入类型 class 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 语言中,它是动态调度的(最近,在运行时)。现在您想将它与隐式/类型 classes (Foo
) / 静态调度的临时多态性(早期,在编译时)混合使用。您必须使用一些运行时工具,例如运行时反射(使用 TypeTag
s 将有关 Node
的编译时信息保存到运行时)、运行时编译。
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
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) 实际上,我想最简单的方法是将隐式参数从方法移动到 class
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
有没有办法测试一个类型是否是一个类型class的成员?例如:
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 is a library for creating GraphQL services in Scala. It has a marshalling subsystem 以有效类型 class、InputUnmarshaller[Node]
的形式交织其中。在代码中可以看到由上下文限定的类型参数:In: InputUnmarshaller
.
概念是消耗输入值并生成输出数据集 a Value production, each element of which needs to be marshalled. The Node
type can be restricted to, for example, io.circe.Json
values, if one is using Circe for the marshalling。
还有a Scala marshaller, which is quite dumb in that it only handles Map
types as being map-like。目标是扩展它以支持案例 classes,例如,通过 Shapeless 和类似地图的类型 class.
(1) 尝试引入类型 class 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 语言中,它是动态调度的(最近,在运行时)。现在您想将它与隐式/类型 classes (Foo
) / 静态调度的临时多态性(早期,在编译时)混合使用。您必须使用一些运行时工具,例如运行时反射(使用 TypeTag
s 将有关 Node
的编译时信息保存到运行时)、运行时编译。
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
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) 实际上,我想最简单的方法是将隐式参数从方法移动到 class
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