How to match function Expr in scala 3 macros - scala

With scala 3 quotes syntax:
'{...}
How to match a function expression?
def macroImpl(expr: Expr[T => Any])(using q: Quotes): Expr[Any] = {
expr match {
case '{/*how to match function here*/} => ???
}
}

A way is:
def macroImpl[T : Type](expr: Expr[T => Any])(using q: Quotes): Expr[Any] = {
expr match {
case '{(x : T) => ${body} : Any } => .... //do something
}
}

Related

Scala 3 (Dotty) Pattern match a function with a macro quotation

I'm trying to get the function name via macros in Scala 3.0.0-M2
The solution that I came up with uses TreeAccumulator
import scala.quoted._
inline def getName[T](inline f: T => Any): String = ${getNameImpl('f)}
def getNameImpl[T](f: Expr[T => Any])(using Quotes): Expr[String] = {
import quotes.reflect._
val acc = new TreeAccumulator[String] {
def foldTree(names: String, tree: Tree)(owner: Symbol): String = tree match {
case Select(_, name) => name
case _ => foldOverTree(names, tree)(owner)
}
}
val fieldName = acc.foldTree(null, Term.of(f))(Symbol.spliceOwner)
Expr(fieldName)
}
When called this code produces the name of the function:
case class B(field1: String)
println(getName[B](_.field1)) // "field1"
I wonder if this can be done in an easier way using quotes.
I guess it's enough to define
def getNameImpl[T: Type](f: Expr[T => Any])(using Quotes): Expr[String] = {
import quotes.reflect._
Expr(TypeTree.of[T].symbol.caseFields.head.name)
}
Actually, I don't use f.
Testing:
println(getName[B](_.field1)) // "field1"
Tested in 3.0.0-M3-bin-20201211-dbc1186-NIGHTLY.
How to access parameter list of case class in a dotty macro
Alternatively you can try
def getNameImpl[T](f: Expr[T => Any])(using Quotes): Expr[String] = {
import quotes.reflect._
val fieldName = f.asTerm match {
case Inlined(
_,
List(),
Block(
List(DefDef(
_,
List(),
List(List(ValDef(_, _, _))),
_,
Some(Select(Ident(_), fn))
)),
Closure(_, _)
)
) => fn
}
Expr(fieldName)
}

Can I generate Scala code from a template (of sorts)?

Can I generate Scala code from a template (of sorts)?
I know how to do this in Racket/Scheme/Lisp, but not in Scala. Is this something Scala macros can do?
I want to have a code template where X varies. If I had this code template:
def funcX(a: ArgsX): Try[Seq[RowX]] =
w.getThing() match {
case Some(t: Thing) => w.wrap(t){Detail.funcX(t, a)}
case _ => Failure(new MissingThingException)
}
and tokens Apple and Orange, a macro would take my template, replace the Xs, and produce:
def funcApple(a: ArgsApple): Try[Seq[RowApple]] =
w.getThing() match {
case Some(t: Thing) => w.wrap(t){Detail.funcApple(t, a)}
case _ => Failure(new MissingThingException)
}
def funcOrange(a: ArgsOrange): Try[Seq[RowOrange]] =
w.getThing() match {
case Some(t: Thing) => w.wrap(t){Detail.funcOrange(t, a)}
case _ => Failure(new MissingThingException)
}
Try macro annotation with tree transformer
#compileTimeOnly("enable macro paradise")
class generate extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro GenerateMacro.impl
}
object GenerateMacro {
def impl(c: whitebox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
val trees = List("Apple", "Orange").map { s =>
val transformer = new Transformer {
override def transform(tree: Tree): Tree = tree match {
case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" if tname.toString.contains("X") =>
val tname1 = TermName(tname.toString.replace("X", s))
val tparams1 = tparams.map(super.transform(_))
val paramss1 = paramss.map(_.map(super.transform(_)))
val tpt1 = super.transform(tpt)
val expr1 = super.transform(expr)
q"$mods def $tname1[..$tparams1](...$paramss1): $tpt1 = $expr1"
case q"${tname: TermName} " if tname.toString.contains("X") =>
val tname1 = TermName(tname.toString.replace("X", s))
q"$tname1"
case tq"${tpname: TypeName} " if tpname.toString.contains("X") =>
val tpname1 = TypeName(tpname.toString.replace("X", s))
tq"$tpname1"
case q"$expr.$tname " if tname.toString.contains("X") =>
val expr1 = super.transform(expr)
val tname1 = TermName(tname.toString.replace("X", s))
q"$expr1.$tname1"
case tq"$ref.$tpname " if tpname.toString.contains("X") =>
val ref1 = super.transform(ref)
val tpname1 = TypeName(tpname.toString.replace("X", s))
tq"$ref1.$tpname1"
case t => super.transform(t)
}
}
transformer.transform(annottees.head)
}
q"..$trees"
}
}
#generate
def funcX(a: ArgsX): Try[Seq[RowX]] =
w.getThing() match {
case Some(t: Thing) => w.wrap(t){Detail.funcX(t, a)}
case _ => Failure(new MissingThingException)
}
//Warning:scalac: {
// def funcApple(a: ArgsApple): Try[Seq[RowApple]] = w.getThing() match {
// case Some((t # (_: Thing))) => w.wrap(t)(Detail.funcApple(t, a))
// case _ => Failure(new MissingThingException())
// };
// def funcOrange(a: ArgsOrange): Try[Seq[RowOrange]] = w.getThing() match {
// case Some((t # (_: Thing))) => w.wrap(t)(Detail.funcOrange(t, a))
// case _ => Failure(new MissingThingException())
// };
// ()
//}
Also you can try approach with type class
def func[A <: Args](a: A)(implicit ar: ArgsRows[A]): Try[Seq[ar.R]] =
w.getThing() match {
case Some(t: Thing) => w.wrap(t){Detail.func(t, a)}
case _ => Failure(new MissingThingException)
}
trait ArgsRows[A <: Args] {
type R <: Row
}
object ArgsRows {
type Aux[A <: Args, R0 <: Row] = ArgsRows[A] { type R = R0 }
implicit val apple: Aux[ArgsApple, RowApple] = null
implicit val orange: Aux[ArgsOrange, RowOrange] = null
}
sealed trait Args
trait ArgsApple extends Args
trait ArgsOrange extends Args
trait Thing
sealed trait Row
trait RowApple extends Row
trait RowOrange extends Row
object Detail {
def func[A <: Args](t: Thing, a: A)(implicit ar: ArgsRows[A]): ar.R = ???
}
class MissingThingException extends Throwable
trait W {
def wrap[R <: Row](t: Thing)(r: R): Try[Seq[R]] = ???
def getThing(): Option[Thing] = ???
}
val w: W = ???
In my opinion, it looks like you could pass your funcX function as a higher-order function. You could also combine it with currying to make a "function factory":
def funcX[A](f: (Thing, A) => RowX)(a: A): Try[Seq[RowX]] =
w.getThing() match {
case Some(t: Thing) => w.wrap(t){f(t,a)}
case _ => Failure(new MissingThingException)
}
Then you could use it to create instances of funcApple or funcOrange:
val funcApple: ArgsApple => Try[Seq[RowX]] = funcX(Detail.funcApple)
val funcOrange: ArgsOrange => Try[Seq[RowX]] = funcX(Detail.funcOrange)
funcApple(argsApple)
funcOrange(argsOrange)
I assumed the signature of Detail.funcApple and Detail.funcOrange is similar to (Thing, X) => RowX, but of course you could use different.
You may not actually need macros to achieve this, you can use a pattern match a generic type like this:
import scala.util.Try
def funcX[A](input :A) :Try[Seq[String]] = input match {
case x :String => Success(List(s"Input is a string: $input, call Detail.funcApple"))
case x :Int => Success(List(s"Input is an int, call Detail.funcOrange"))
}
scala> funcX("apple")
res3: scala.util.Try[Seq[String]] = Success(List(Input is a string: apple, call Detail.funcApple))
scala> funcX(11)
res4: scala.util.Try[Seq[String]] = Success(List(Input is an int, call Detail.funcOrange))

enrich PartialFunction with unapply functionality

PartialFunction is a natural extractor, its lift method provides exact extractor functionality. So it would be very convenient to use partial functions as extractors. That would allow to combine pattern matching expressions in more complicated way than plain orElse that is available for PartialFunction
So I tried to use pimp my library approach and had failed
Here goes update: As #Archeg shown, there is another approach to conversion that works. So I'm including it to the provided code.
I'm tried also some more complex solutions and they failed
object Test {
class UnapplyPartial[-R, +T](val fun : PartialFunction[R,T]) {
def unapply(source : R) : Option[T] = fun.lift(source)
}
implicit def toUnapply[R,T](fun : PartialFunction[R,T]) : UnapplyPartial[R,T] = new UnapplyPartial(fun)
class PartialFunOps[-R, +T](val fun : PartialFunction[R,T]) {
def u : UnapplyPartial[R, T] = new UnapplyPartial(fun)
}
implicit def toPartFunOps[R,T](fun : PartialFunction[R,T]) : PartialFunOps[R,T] = new PartialFunOps(fun)
val f : PartialFunction[String, Int] = {
case "bingo" => 0
}
val u = toUnapply(f)
def g(compare : String) : PartialFunction[String, Int] = {
case `compare` => 0
}
// error while trying to use implicit conversion
def testF(x : String) : Unit = x match {
case f(i) => println(i)
case _ => println("nothing")
}
// external explicit conversion is Ok
def testU(x : String) : Unit = x match {
case u(i) => println(i)
case _ => println("nothing")
}
// embedded explicit conversion fails
def testA(x : String) : Unit = x match {
case toUnapply(f)(i) => println(i)
case _ => println("nothing")
}
// implicit explicit conversion is Ok
def testI(x : String) : Unit = x match {
case f.u(i) => println(i)
case _ => println("nothing")
}
// nested case sentences fails
def testInplace(x : String) : Unit = x match {
case { case "bingo" => 0 }.u(i) => println(i)
case _ => println("nothing")
}
// build on the fly fails
def testGen(x : String) : Unit = x match {
case g("bingo").u(i) => println(i)
case _ => println("nothing")
}
// implicit conversion without case is also Ok
def testFA(x : String) : Option[Int] =
f.unapply(x)
}
I got the following error messages:
UnapplyImplicitly.scala:16: error: value f is not a case class, nor does it have an unapply/unapplySeq member
case f(i) => println(i)
UnapplyImplicitly.scala:28: error: '=>' expected but '(' found.
case toUnapply(f)(i) => println(i)
This errors may be avoided with supposed form as TestI shown. But I'm curious if it is possible to avoid testInplace error:
UnapplyImplicitly.scala:46: error: illegal start of simple pattern
case { case "bingo" => 0 }.u(i) => println(i)
^
UnapplyImplicitly.scala:47: error: '=>' expected but ';' found.
case _ => println("nothing")
UnapplyImplicitly.scala:56: error: '=>' expected but '.' found.
case g("bingo").u(i) => println(i)
^
I'm not sure what you are trying to achieve in the end, but as far as I understand extractors should always be objects, there is no way you can get it with a class. It is actually called Extractor Object in the documentation. Consider this:
class Wrapper[R, T](fun: PartialFunction[R, T]) {
object PartialExtractor {
def unapply(p: R): Option[T] = fun.lift(p)
}
}
implicit def toWrapper[R,T](fun : PartialFunction[R,T]) : Wrapper[R, T] = new Wrapper(fun)
val f : PartialFunction[String, Int] = {
case "bingo" => 0
}
def testFF(x : String) : Unit = x match {
case f.PartialExtractor(i) => println(i)
case _ => println("nothing")
}
Update
The best I could think of:
def testInplace(x : String) : Unit ={
val ff = { case "bingo" => 0 } : PartialFunction[String, Int]
x match {
case ff.PartialExtractor(Test(i)) => println(i)
case "sd" => println("nothing") }
}

Case insensitive pattern matching for strings

match (str) {
case "String1" => ???
case "String2" => ???
}
This is case sensitive matching. How to write case insensitive matching? I know I can call toLowerCase for each branch, but I want more elegant solution.
Basic approach:
You could use Pattern Guards and Regular Expressions
str match {
case s if s matches "(?i)String1" => 1
case s if s matches "(?i)String2" => 2
case _ => 0
}
Sophisticated method:
Implicits with String Interpolation and Regex
implicit class CaseInsensitiveRegex(sc: StringContext) {
def ci = ( "(?i)" + sc.parts.mkString ).r
}
def doStringMatch(str: String) = str match {
case ci"String1" => 1
case ci"String2" => 2
case _ => 0
}
Some example usage in the REPL:
scala> doStringMatch("StRINg1")
res5: Int = 1
scala> doStringMatch("sTring2")
res8: Int = 2
Easy solution:
val str = "string1"
str toUpperCase match (str) {
case "STRING1" => ???
case "STRING2" => ???
}
Another approach that does not depend on regexes or interpolaters:
implicit class StringExtensions(val s: String) extends AnyVal {
def insensitive = new {
def unapply(other: String) = s.equalsIgnoreCase(other)
}
}
val test1 = "Bye".insensitive
val test2 = "HELLo".insensitive
"Hello" match {
case test1() => println("bad!")
case test2() => println("sweet!")
case _ => println("fail!")
}
Here is another way using interpolaters but no regex:
implicit class StringInterpolations(sc: StringContext) {
def ci = new {
def unapply(other: String) = sc.parts.mkString.equalsIgnoreCase(other)
}
}
"Hello" match {
case ci"Bye" => println("bad!")
case ci"HELLO" => println("sweet!")
case _ => println("fail!")
}
The above can also be used to pattern match inside case classes e.g.:
case class Dog(name: String)
val fido = Dog("FIDO")
fido match {
case Dog(ci"fido") => "woof"
case _ => "meow :("
}

Can I use a class variable in a Scala match statement?

Say I have something like this:
obj match {
case objTypeOne : TypeOne => Some(objTypeOne)
case objTypeTwo : TypeTwo => Some(objTypeTwo)
case _ => None
}
Now I want to generalise, to pass in one of the types to match:
obj match {
case objTypeOne : clazz => Some(objTypeOne)
case objTypeTwo : TypeTwo => Some(objTypeTwo)
case _ => None
}
But this isn't allowed, I think for syntactic rather than semantic reasons (although I guess also that even though the clazz is a Class[C] the type is erased and so the type of the Option will be lost).
I ended up with:
if(clazzOne.isAssignableFrom(obj.getClass)) Some(clazz.cast(obj))
if(obj.isInstanceOf[TypeTwo]) Some(obj.asInstanceOf[TypeTwo])
None
I just wondered if there was a nicer way.
You could define an extractor to match your object:
class IsClass[T: Manifest] {
def unapply(any: Any): Option[T] = {
if (implicitly[Manifest[T]].erasure.isInstance(any)) {
Some(any.asInstanceOf[T])
} else {
None
}
}
}
So let's test it:
class Base { def baseMethod = () }
class Derived extends Base
val IsBase = new IsClass[Base]
def test(a:Any) = a match {
case IsBase(b) =>
println("base")
b.baseMethod
case _ => println("?")
}
test(new Base)
test(1)
You will have to define a val for your extractor, you can't inline IsBase, for example. Otherwise it would be interpreted as an extractor.
You could use pattern guards to achieve that. Try something like this:
obj match {
case objTypeTwo : TypeTwo => Some(objTypeTwo)
case objTypeOne if clazz.isAssignableFrom(objTypeOne.getClass) => Some(clazz.cast(objTypeOne))
case _ => None
}
You can use a local type alias for that:
def matcher[T](obj: Any)(implicit man: Manifest[T]) = {
val instance = man.erasure.newInstance.asInstanceOf[AnyRef]
type T = instance.type // type alias
obj match {
case objTypeOne : T => "a"
case objTypeTwo : TypeTwo => "b"
case _ => "c"
}
}
scala> matcher[TypeOne](TypeOne())
res108: java.lang.String = a
scala> matcher[TypeTwo](TypeOne())
res109: java.lang.String = c
UPDATE: Aaron Novstrup has pointed out that singleton type will only work if man.erasure.newInstance==obj (see ยง3.2.1 of the spec)