将字符串转换为Lambda

huangapple go评论79阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年6月26日 18:32:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76555850.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定