Home > Blockchain >  test for membership in Scala type class
test for membership in Scala type class

Time:10-15

Is there a way to test whether a type is a member of a type class? E.g.:

trait Foo[A]

trait Marshaller[Node] {
  def isFoo(n: Node): Boolean
}

class MyMarshaller[Node] extends Marshaller[Node] {
  override def isFoo(n: Node): Boolean = ???
}

Obviously there are solutions for executing code when the membership exists:

def isFoo(a: A)(implicit ev: Foo[A]) = // do something Foo-like with `a`

In my use case, I'm overriding the isFoo method, so I also can't change its signature.


the real-world problem

Sangria is a library for creating GraphQL services in Scala. It has a marshalling subsystem woven into it in the form of what is effectively a type class, InputUnmarshaller[Node]. In the code one can see type parameters qualified by context: In: InputUnmarshaller.

The notion is that one consumes input values and produces the output data set as 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.

There is also a Scala marshaller, which is quite dumb in that it only handles Map types as being map-like. The goal is to expand it to support case classes, for instance, via Shapeless and a map-like type class.

CodePudding user response:

Try to introduce type 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()

Testing:

implicit val intFoo: Foo[Int] = null

isFoo[Int] // true
isFoo[String] // false

Actually, I guess my isFoo is just a more complicated variant of @LuisMiguelMejíaSuárez's def isFoo[A](implicit ev: Foo[A] = null): Boolean = Option(ev).isDefined


You want to define behavior of Marshaller via inheritance / subtyping polymorphism. And in JVM languages it's dispatched dynamically (lately, at runtime). Now you want to mix it with implicits / type classes (Foo) / ad hoc polymorphism dispatched statically (early, at compile time). You'll have to use some runtime tools like runtime reflection (to persist compile-time information about Node to runtime with TypeTags), runtime compilation.

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?


If you just want to check that Node is Map[String, _] then just runtime reflection is enough

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

See also 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

In Scala 3 you could use 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

Actually, I guess the simplest would be to move implicit parameter from method to 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

@LuisMiguelMejíaSuárez's idea with default implicit also can be used in such case

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
  • Related