英文:
Scala: Convert string to lambda
问题
I want to be able to parse strings like "x => x + 1"
and be able to actually use it as a lambda function like this:
val lambda = parseLambda(string)
val numbers = List(1, 2, 3)
val result = numbers.map(lambda) // Should be [2, 3, 4]
I have the following implementation for the parseLambda
function:
val toolbox = runtimeMirror(getClass.getClassLoader).mkToolBox()
val tree = toolbox.parse(string)
val lambdaFunction = toolbox.compile(tree)().asInstanceOf[Any => Any]
lambdaFunction
}
It works for strings like "(x: Int) => x * 2"
or "(s: String) => s.split(" ")"
but if I omit the types e.g. "x => x * 2"
or "s => s.split(" ")"
I get the following error:
';' expected but '=>' found.
Is there a way to be able to omit the types in the string? Any help would be appreciated!
英文:
I want to be able to parse strings like "x => x + 1"
and be able to actually use it as a lambda function like this:
val string = "x => x + 1"
val lambda = parseLambda(string)
val numbers = List(1, 2, 3)
val result = numbers.map(lambda) // Should be [2, 3, 4]
I have the following implementation for the parseLambda
function:
def parseLambda(string: String): Any => Any = {
val toolbox = runtimeMirror(getClass.getClassLoader).mkToolBox()
val tree = toolbox.parse(string)
val lambdaFunction = toolbox.compile(tree)().asInstanceOf[Any => Any]
lambdaFunction
}
It works for strings like "(x: Int) => x * 2"
or "(s: String) => s.split(" ")"
but if I omit the types e.g. "x => x * 2"
or "s => s.split(" ")"
I get the following error
';' expected but '=>' found.
Is there a way to be able to omit the types in the string? Any help would be appreciated!
答案1
得分: 6
Without type hint x => x * 2
would fail to compile in both Scala 2 (https://scastie.scala-lang.org/ChqbxOxRSvGhFTsXeyhOWA) and Scala 3 (https://scastie.scala-lang.org/1uYVoRXLTeKEwfoPCwrKzQ). This is simply bad code. toolkit.parse
is returning rather unhelpful message, but there is no magic way in which the compiler would guess what should the input be.
However in your code there is something which knows what the type should be: the type of values in numbers
.
trait TypeName[A] {
def name: String
}
def parseLambda[Input: TypeName](string: String): Input => Any = {
val code = s"""
val result: ${implicitly[TypeName[Input]].name} => Any = ${string}
result
""".stripMargin
val toolbox = runtimeMirror(getClass.getClassLoader).mkToolBox()
val tree = toolbox.parse(code)
val lambdaFunction = toolbox.compile(tree)().asInstanceOf[Input => Any]
lambdaFunction
}
Then it should work:
val string = "x => x + 1"
val lambda = parseLambda[Int](string) // Int hinted
val numbers = List(1, 2, 3)
val result = numbers.map(lambda) // Should be [2, 3, 4]
// or
numbers.map(parseLambda(string)) // Int inferred
That is, it should work as long as you provide the right TypeName[A]
instance.
In Scala 2 it should work with something similar to:
object TypeName {
import scala.reflect.runtime.universe._
implicit def provide[A](implicit tag: WeakTypeTag[A]): TypeName[A] =
new TypeName[A] {
def name: String = tag.toString() // should work for simple cases
}
}
and for Scala 3 (your code looks like Scala 2, but in case you wanted to port it to 3) something like:
object TypeName {
import scala.quoted.*
def provideImpl[A: Type](using quotes: Quotes): Expr[TypeName[A]] =
import quotes.*, quotes.reflect.*
'{
new TypeName[A] {
def name: String = ${ TypeRepr.of[A].show(using TypeReprCode) }
}
}
inline given provide[A]: TypeName[A] = ${ TypeName.provideImpl[A] }
}
(Disclaimer, I haven't compiled it, so it should be something like that, but might need some adjustments).
What if this type cannot be provided because you don't know? (E.g. if this lambda was sent to you?)
Well, if you don't know the type, the compiler wouldn't know as well, so you simply cannot do that.
英文:
Without type hint x => x * 2
would fail to compile in both Scala 2 (https://scastie.scala-lang.org/ChqbxOxRSvGhFTsXeyhOWA) and Scala 3 (https://scastie.scala-lang.org/1uYVoRXLTeKEwfoPCwrKzQ). This is simply bad code. toolkit.parse
is returning rather unhelpful message, but there is no magic way in which compiler would guess what should the input be.
However in your code there is something which knows what the type should be: the type of values in numbers
.
trait TypeName[A] {
def name: String
}
def parseLambda[Input: TypeName](string: String): Input => Any = {
val code = s"""val result: ${implicitly[TypeName[Input]].name} => Any = ${string}
|result
|""".stripMargin
val toolbox = runtimeMirror(getClass.getClassLoader).mkToolBox()
val tree = toolbox.parse(code)
val lambdaFunction = toolbox.compile(tree)().asInstanceOf[Input => Any]
lambdaFunction
}
Then it should work:
val string = "x => x + 1"
val lambda = parseLambda[Int](string) // Int hinted
val numbers = List(1, 2, 3)
val result = numbers.map(lambda) // Should be [2, 3, 4]
// or
numbers.map(parseLambda(string)) // Int inferred
That is, it should work as long as you provide the right TypeName[A]
instance.
In Scala 2 it should works with something similar to:
object TypeName {
import scala.reflect.runtime.universe._
implicit def provide[A](implicit tag: WeakTypeTag[A]): TypeName[A] =
new TypeName[A] {
def name: String = tag.toString() // should work for simple cases
}
}
and for Scala 3 (your code looks like Scala 2, but in case you wanted to port it to 3) something like:
object TypeName {
import scala.quoted.*
def provideImpl[A: Type](using quotes: Quotes): Expr[TypeName[A]] =
import quotes.*, quotes.reflect.*
'{
new TypeName[A] {
def name: String = ${ TypeRepr.of[A].show(using TypeReprCode) }
}
}
inline given provide[A]: TypeName[A] = ${ TypeName.provideImpl[A] }
}
(Disclaimer, I haven't compiled it, so it should be something like that, but might need some adjustments).
What if this type cannot be provided because you don't know? (E.g. if this lambda was sent to you?)
Well, if you don't know the type compiler wouldn't know as well, so you simply cannot do that.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论