Consider a simple task. Just show the whole expression like:
a operator b = c
In Common Lisp it might look like this:
(defun applyOp (a b op)
(format t "~S ~S ~S = ~S" a op b (apply op (list a b))))
$ (applyOp 1 2 #'logand)
1 #<FUNCTION LOGAND> 2 = 0
In Scala it seems not so trivial:
def applyOperator(x: Int, y: Int, op: (Int, Int) => Int) = {
println(x + s" $op " + y + " = " + op(x,y))
}
scala> applyOperator(1, 2, _ & _)
1 <function2> 2 = 0 // how to get the function name?
What's the name of <function2>
?
答案 0 :(得分:5)
另一个功能齐全的方法是简单的macro。
假设您编写了表达式和typename的最简单的宏提取字符串表示形式:
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
class VerboseImpl(val c: Context) {
import c.universe._
def describe[T: c.WeakTypeTag](expr: c.Expr[T]): c.Expr[(String, String, T)] = {
val repr = expr.tree.toString
val typeName = weakTypeOf[T].toString
c.Expr[(String, String, T)](q"($repr, $typeName, $expr)")
}
}
object Verbose{
def apply[T](expr: T): (String, String, T) = macro VerboseImpl.describe[T]
}
接下来在另一个来源(可以在另一个子项目中使用),你可以写
val a = 2
val b = 3
println(Verbose(a + b + 3))
并查看魔术字符串
(a.+(b).+(3),Int,8)
从这一点开始,您可以增强宏以显示有关方法,参数,类型等的任何信息......
请注意,宏在编译时进行评估。因此,Verbose.apply
调用的开销就像创建具有两个字符串常量的元组一样,所以尽管它是唯一的无平衡,可扩展的方法,但它绝对是性能最高的
答案 1 :(得分:4)
a op b
在语法上等同于a.op(b)
(除非名称op以冒号结尾,在这种情况下它等同于b.op(a)
)。
这当然与a.callOperator(op,b)
完全不同,您需要名称op
。在scala中,每个运算符都有不同的方法,并且在实现此运算符的方法内访问运算符的名称是没有意义的。
callOperator(op,b)的好处在于,您可以在同一个地方实现所有运算符,可能非常通用和简洁。
如果编译器将使用正确的参数检查您是否只能调用实际实现的那个方法,那么每个方法都有一个好处。此外,结果的类型将在编译时知道。
Scala是一种打字语言,非常喜欢第二种语言。
然而,在该语言的某个远程角落,有一种方法可以将对编译时不可用的方法的调用转换为某个备份调用,从而获取方法(或运算符)名称和参数。
调用的目标(即左操作数)必须扩展特征Dynamic。由于这是一个相当特殊的功能,您必须使用import scala.language.dynamics
。
然后,每次对不存在的方法的调用都将被重写为对方法applyDynamic
的调用,带有两个参数列表,第一个获取方法的名称,第二个获取实际的参数依赖于是否以及如何定义applyDynamic
,编译器可能允许也可能不允许这种重写。
这是一个例子
case class A(string name) {
def applyDynamic(methodOrOperatorName: String)(arg: Any) : A {
A(s"($name $methodOrOperatorName ${arg.mkString(", ")})
}
}
这里我选择在调用中只允许一个arg,如果我只想要二元运算符(但没有办法区分op b和a.op(b),scala认为它们是等价的),这很好。否则我会写args: Any*
。我允许任何类型(Any),但我可以限制它,例如force arg: A
。我没有被强制使用A作为结果类型,但如果我的结果类型不知道是动态的,我将无法链接a op b op' c
。
可能存在一些问题。仅当编译器无法通过其他方式编译a op b
时,才会调用applyDynamic。如果有一些隐式转换使得op可用,那么这将具有优先权。例如,Predef为每个对象提供+
,用于字符串连接。因此,如果您的运算符为+
,则会调用此函数。为避免这种情况,您可以在A:
+
def +(arg: Any): A = applyDynamic("+")(arg)
// Arg and result type as in applyDynamic
这会使+安全,但通过隐式在呼叫站点提供的任何运营商也将优先考虑。
如果你有一个有限的允许操作员列表,你可能更愿意完全避免魔法。
class A {
def +(arg: A): A = callOp("+", b) // or another signature
def -(arg: A): A = callOp("-", b)
def callOp(name: String, arg: A): A = {...}
}
答案 2 :(得分:3)
如果你的Function2s是对象,你可以得到这样的东西。它有点微妙,但我发现在一些固定数量的功能对象的DSL中它很方便。例如,
InterfaceBasedMBeanInfoAssembler