Is it possible to distinguish Scala-3 enums and sealed traits using Mirrors or even Macros?
transparent inline def isScalaEnum[A]: Boolean = ${ isScalaEnumImpl[A] }
private def isScalaEnumImpl[A: Type](using q: Quotes): Expr[Boolean] = ???
For example, how do you implement the above macro?
sealed trait T
case class A(x: Int) extends T
case class B(x: String) extends T
enum Color(val rgb: Int):
case Red extends Color(1)
case Green extends Color(2)
isScalaEnum[T] should be false
isScalaEnum[Color] should be true
You could do it with <:< and NotGiven:
<:< reflect.Enum for checking whether your type is subclass of scala.reflect.Enum (which is true only for Scala 3 enums)
NotGiven for translating the absence of <:<-evidence into a false
Here is how it can be implemented:
import scala.util.NotGiven
case class IsEnum[X](value: Boolean)
given isEnum[X](using X <:< reflect.Enum): IsEnum[X] = IsEnum(true)
given isNotEnum[X](using NotGiven[X <:< reflect.Enum]): IsEnum[X] = IsEnum(false)
inline def isScalaEnum[X](using inline ev: IsEnum[X]): Boolean = ev.value
Here is how it can be used:
enum Foo:
case Bar
sealed trait NotFoo
#main def demo(): Unit =
println(isScalaEnum[Foo]) // true
println(isScalaEnum[NotFoo]) // false
Update
If you want to have more precise type information at compile time, just transparently inline all the things:
import scala.util.NotGiven
case class IsEnum[X](value: Boolean)
transparent inline given isEnum[X](using X <:< reflect.Enum): IsEnum[X] =
IsEnum(true)
transparent inline given isNotEnum[X](using NotGiven[X <:< reflect.Enum]): IsEnum[X] =
IsEnum(false)
transparent inline def isScalaEnum[X](using inline ev: IsEnum[X]): ev.value.type =
ev.value
It then behaves similarly to the test cases that you've mentioned in your own answer, with Null and Nothing being notable exceptions:
enum Foo:
case Bar
enum FooS(x:String):
case Bar extends FooS("str")
sealed trait NotFoo
inline val a: true = isScalaEnum[Foo]
inline val b: true = isScalaEnum[Foo.Bar.type]
inline val c: true = isScalaEnum[FooS]
inline val d: true = isScalaEnum[FooS.Bar.type]
inline val e: false = isScalaEnum[NotFoo]
inline val f: false = isScalaEnum[Int]
inline val g: false = isScalaEnum[String]
inline val x: true = isScalaEnum[Null]
inline val y: true = isScalaEnum[Nothing]
Thanks to #Gaël J's comment, here is what I came up with. Unfortunately, it uses Macro, but a simple one.
In one file:
import scala.quoted.*
transparent inline def isScalaEnum[A]: Boolean =
${ isScalaEnumImpl[A] }
private def isScalaEnumImpl[A: Type](using q: Quotes): Expr[Boolean] =
import q.reflect.*
TypeRepr.of[A].asType match
case '[Null] => Expr(false) // Return false for Null, regardless of whether explicit-nulls are enabled.
case '[Nothing] => Expr(false) // I prefer false for Nothing
case '[reflect.Enum] => Expr(true)
case _ => Expr(false)
Here are my test:
enum Foo:
case Bar
enum FooS(x:String):
case Bar extends FooS("str")
sealed trait NotFoo
#main
def demo(): Unit =
inline val a: true = isScalaEnum[Foo]
inline val b: true = isScalaEnum[Foo.Bar.type]
inline val c: true = isScalaEnum[FooS]
inline val d: true = isScalaEnum[FooS.Bar.type]
inline val e: false = isScalaEnum[NotFoo]
inline val f: false = isScalaEnum[Int]
inline val g: false = isScalaEnum[Null]
inline val h: false = isScalaEnum[Nothing]
Please let me know if you think there is an easier/better solution, or there is something wrong with what I provided here.
EDIT:
Thanks to #Andrey Tyukin comments/answers, I changed the macro to treat Null and Nothing explicitly.
Thanks to #Andrey Tyukin and #Gaël J, here is a simpler version.
transparent inline def isScalaEnum[X]: Boolean = inline compiletime.erasedValue[X] match
case _: Null => false // Return false for Null, regardless of whether explicit-nulls are enabled.
case _: Nothing => false // I prefer false for Nothing
case _: reflect.Enum => true
case _ => false
Here are some tests:
enum Foo:
case Bar
enum FooS(x:String):
case Bar extends FooS("str")
sealed trait NotFoo
inline val a: true = isScalaEnum[Foo]
inline val b: true = isScalaEnum[Foo.Bar.type]
inline val c: true = isScalaEnum[FooS]
inline val d: true = isScalaEnum[FooS.Bar.type]
inline val e: false = isScalaEnum[NotFoo]
inline val f: false = isScalaEnum[Int]
inline val g: false = isScalaEnum[String]
inline val x: false = isScalaEnum[Null]
inline val y: false = isScalaEnum[Nothing]
Related
Is there a clean way to access the default values of a case class fields when performing type class derivation in Scala 3 using Mirrors? For example:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels will be set to ("s", "i", "d"). Is there anything like: (Some["bar"], None, Some[3.141592653589793])?
If not could this be achieved using Macros? Can I use the Mirrors and Macros simultaneously to derive a type class instance?
You'll have to write a macro working with methods named like <init>$default$1, <init>$default$2, ... in companion object
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
Mirrors and macros can work together:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
Usage:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
As Dmytro suggests, information is carried in methods <init>default$x of the class companion object.
However, Quotes discourages accessing a symbol's tree in a macro:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628.
Symbol's tree is lost, unless program is compiled with -Yretain-trees)
It is better to let the macro evaluate <init>default$x, rather than copy the right hand side of its definition.
One can do so by expressing terms as :
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)
Is there a clean way to access the default values of a case class fields when performing type class derivation in Scala 3 using Mirrors? For example:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels will be set to ("s", "i", "d"). Is there anything like: (Some["bar"], None, Some[3.141592653589793])?
If not could this be achieved using Macros? Can I use the Mirrors and Macros simultaneously to derive a type class instance?
You'll have to write a macro working with methods named like <init>$default$1, <init>$default$2, ... in companion object
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
Mirrors and macros can work together:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
Usage:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
As Dmytro suggests, information is carried in methods <init>default$x of the class companion object.
However, Quotes discourages accessing a symbol's tree in a macro:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628.
Symbol's tree is lost, unless program is compiled with -Yretain-trees)
It is better to let the macro evaluate <init>default$x, rather than copy the right hand side of its definition.
One can do so by expressing terms as :
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)
I want to override method in enum class, something as the following example mentioned in Add enum construct
enum class Option[+T] extends Serializable {
def isDefined: Boolean
}
object Option {
def apply[T](x: T) = if (x != null) Some(x) else None
case Some[+T](x: T) {
def isDefined = true
}
case None {
def isDefined = false
}
}
However, the codes does not compiles in Scala 3.0.
How to change the code to make it work? Thanks.
EDIT Thanks for #Mario Galic's answer.
I want to grouped a series of compression method as an enum, as following:
enum CompressionMethod:
def compress(out: OutputStream): OutputStream
def decompress(in: InputStream): InputStream
case GZIP extends CompressionMethod: ...
case BZIP extends CompressionMethod: ...
case NONE extends CompressionMethod: ...
It seems ugly if I packed all implementation as:
enum CompressionMethod:
def compress(out: OutputStream): OutputStream = this match
case NONE => out
case BZIP => a lot codes ..
case GZIP => a lot codes ..
case ...
Moreover, I need to modify the compress method if I add another case later other than just add an other case instance itself. Something like:
enum CompressionMethod:
def compress(out: OutputStream): OutputStream
case GZIP extends CompressionMethod:
override def compress(out: OutputStream): OutputStream =
...
case BZIP extends CompressionMethod:
override def compress(out: OutputStream): OutputStream =
...
One tip is to check tests for most up-to-date usage, for example, tests/run/enum-Option1.scala
$ scala3-repl
scala> enum Option1[+T]:
| case Some1(x: T)
| case None1
|
| def isDefined: Boolean = this match
| case None1 => false
| case _ => true
|
// defined class Option1
scala> import Option1.*
scala> Some1(42).isDefined
val res0: Boolean = true
scala> None1.isDefined
val res1: Boolean = false
Assume I have a trait which looks something like this
trait MyTrait {
val x: Option[String] = None
val y: Option[String] = None
}
Post defining the trait I extend this trait to a class MyClass which looks something like this
case class MyClass(
override val x: Option[String] = None,
override val y: Option[String] = None,
z: Option[String] = None
) extends MyTrait
Now I need to find if any other property other than the properties extended by MyTrait is not None. In the sense if I need to write a method which is called getClassInfo which returns true/false based upon the values present in the case class. In this case it should return true if z is Non optional. My getClassInfo goes something like this
def getClassInfo(myClass: MyClass): Boolean = {
myClass
.productIterator
.filterNot(x => x.isInstanceOf[MyTrait])
.exists(_.isInstanceOf[Some[_]])
}
Ideally this should filter out all the fields which are not a part of Mytrait and return me z in this case.
I tried using variance, However It seems like isInstanceOf doesn't take the same
filterNot(x => x.isInstanceOf[+MyTrait])
However this cannot be possible
val a = getClassInfo(MyClass()) //Needs to return false
val b = getClassInfo(MyClass(Some("a"), Some("B"), Some("c"))) //returns true
val c = getClassInfo(MyClass(z = Some("z"))) //needs to return true
val d = getClassInfo(MyClass(x = Some("x"), y = Some("y"))) // needs to return false
The simple answer is to declare an abstract method that gives the result you want and override it in the subclass:
trait MyTrait {
def x: Option[String]
def y: Option[String]
def anyNonEmpty: Boolean = false
}
case class MyClass(x: Option[String] = None, y: Option[String] = None, z: Option[String] = None) extends MyTrait {
override def anyNonEmpty = z.nonEmpty
}
You can then call anyNonEmpty on your object to get the getClassInfo result.
Also note that I've used def here in the trait because val in a trait is generally a bad idea because of initialisation issues.
If you really need reflection you can try
import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
def getClassInfo(myClass: MyClass): Boolean = {
def fields[A: TypeTag] = typeOf[A].members.collect {
case m: MethodSymbol if m.isGetter && m.isPublic => m
}
val mtFields = fields[MyTrait]
val mcFields = fields[MyClass]
val mtFieldNames = mtFields.map(_.name).toSet
val mcNotMtFields = mcFields.filterNot(f => mtFieldNames.contains(f.name))
val instanceMirror = currentMirror.reflect(myClass)
val mcNotMtFieldValues = mcNotMtFields.map(f => instanceMirror.reflectField(f).get)
mcNotMtFieldValues.exists(_.isInstanceOf[Some[_]])
}
val a = getClassInfo(MyClass()) //false
val b = getClassInfo(MyClass(Some("a"), Some("B"), Some("c"))) //true
val c = getClassInfo(MyClass(z = Some("z"))) //true
val d = getClassInfo(MyClass(x = Some("x"), y = Some("y")))//false
I don't really get this little thingy. I have an abstract class Box
with several sub-classes for different types. For example
abstract class Box
class StringBox(val sValue : String) extends Box
The apply method in the companion object for Box is simple:
object Box{
def apply(s: String) = new StringBox(s)
def apply(b: Boolean) = new BooleanBox(b)
def apply(d: Double) = new DoubleBox(d)
}
so I can write
val sb = Box("StringBox)
Okay, writing unapply makes some trouble. My first idea was to use pattern matching on the type, like this this:
def unapply(b: Box) = b match {
case sb: StringBox => Some(sb.sValue)
case bb: BooleanBox => Some(bb.bValue)
case db: DoubleBox => Some(db.dValue)
case _ => None
}
Which simply doesn't work because of type erasures.
Second attempt was a generic Box[T] with type T and an abstract type member re-defined
in each sub classes. For instance:
abstract class Box[T] {def value : T}
class StringBox(val sValue : String) extends Box[String] {
override def value : String = sValue
}
Consequently, I can re write my unapply as:
def unapply[T](b: Box[T]) = b match {
case sb: Box[String] => Some(sb.value)
case bb: Box[Boolean] => Some(bb.value)
case db: Box[Double] => Some(db.value)
case _ => None
Unfortunately, this doesn't work either. So I guess the explicit type reference
in Box[String] gets erased as well so I need to use a type manifest instead.
Maybe something like:
def unapply[T](b: Box[_])(implicit target: Manifest[T]): Option[T] = {
if(b.value == target) Some(b.value.asInstanceOf[T])
else None
}
This code compiles (2.10) but still does not the desired implicit conversion.
Why?
Simple question, is there a way to do value extraction without using reflection
or a manifest?
What really boggles me is the question if there is a simple(r) way to combine
polymorphism and pattern matching? If not, are there other ways in Scala to
accomplish a similar effect?
Any idea or suggestions?
Thank you very much.
Prolly you can try this.. :)
abstract class Box[T](val v: T)
object Box {
def apply(s: String) = new StringBox(s)
def apply(b: Boolean) = new BooleanBox(b)
def apply(d: Double) = new DoubleBox(d)
}
class StringBox(sValue: String) extends Box(sValue)
object StringBox {
def unapply(b: StringBox) = Some(b.v)
}
class BooleanBox(sValue: Boolean) extends Box(sValue)
object BooleanBox {
def unapply(b: BooleanBox) = Some(b.v)
}
class DoubleBox(sValue: Double) extends Box(sValue)
object DoubleBox {
def unapply(b: DoubleBox) = Some(b.v)
}
You can use it as --
def useCase[T](box: Box[T]) = box match {
case StringBox("StringBoxxx") => "I found the StringBox!"
case StringBox(s) => "Some other StringBox"
case BooleanBox(b) => {
if (b) "Omg! its true BooleanBox !"
else "its false BooleanBox :("
}
case DoubleBox(x) => {
if (x > 3.14) "DoubleBox greater than pie !"
else if (x == 3.14) "DoubleBox with a pie !"
else "DoubleBox less than a pie !"
}
case _ => "What is it yaa ?"
}
useCase(Box("StringBoxxx")) //> res0: String = I found the StringBox!
useCase(Box("Whatever !")) //> res1: String = Some other StringBox
useCase(Box(true)) //> res2: String = Omg! its true BooleanBox !
useCase(Box(false)) //> res3: String = its false BooleanBox :(
useCase(Box(4)) //> res4: String = DoubleBox greater than pie !
useCase(Box(3.14)) //> res5: String = DoubleBox with a pie !
useCase(Box(2)) //> res6: String = DoubleBox less than a pie !