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 TypeTag
s), 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