在Python中,我可以做这样的事情:
def wrap(f):
def wrapper(*args, **kwargs):
print "args: ", args, kwargs
res = f(*args, **kwargs)
print "result: ", res
return res
return wrapper
这使我可以包装任何函数,而不管它们采用什么参数。例如:
In [8]: def f(thing):
print "in f:", thing
return 3
In [9]: wrapped_f = wrap(f)
In [10]: wrapped_f(2)
args: (2,) {}
in f: 2
result: 3
Out[10]: 3
在Scala中有没有类似的方法(编写一个可以应用于任何函数的包装器,无论其输入/输出类型如何)?
您当然可以用宏来实现这一点。您可以将方法调用转换为具有部分应用程序的函数:
object Foo {
def bar(i: Int): Int = i + 1
}
val fn = Foo.bar _
defined object Foo
fn: Int => Int = <function1>
现在您有了一个对象,在本例中为Function1[Int, Int]
类型,您可以将其传递给Scala宏,它将是这样的(未测试):
object DecoratorMacros {
import reflect.macros.blackbox
import language.experimental.macros
def decorate[A <: Function](fn: A): [A] = macro decorate_impl[A]
def decorate_impl[A: c.WeakTypeTag](c: blackbox.Context) = {
import c.universe._
val type = weakTypeOf[A]
...
}
}
在宏的主体中,您可以检查fn: A
的整个类型签名,其中将包括参数。然后,您可以编写代码来实现所需的副作用,并返回一个可以调用的函数。类似这样的东西:
DecoratorMacros.decorate(Foo.bar _)(42)
宏相当复杂,但如果你认为这是一条你想走的路,我可以详细说明。
这里有一个基本问题:在Scala中,你必须知道函数应该得到什么参数,并实际传递它们,这样编译器才能确保类型匹配。
假设有def f(a: List[Int], b: String) = ...
和def g(args: Any*) = f(args)
。这不会编译!(Any*
表示具有任何类型的任意数量的对象)。问题是Any*
仍然只是一个单独的自变量,它实际上被翻译成一种Array
。
为了更清楚地说明这一点,您可以考虑一个示例情况:您使用一些函数f(a: String, b: String)
调用了wrap(f)
。然后你得到了包装器的输出,它将以某种方式接受任何数量的任何类型的参数,然后你进行了wrapper_f(List(1), "a")
调用。在这种情况下,wrapper_f(...)
调用应该是正确的,但在包装器内部,包装的函数有一个完全不同的参数列表,不能接受List[Int]
和String
。因此,你会在运行时得到"类型错误",这在静态类型编程语言中(或者至少在Scala中)是不可能的。