In Python I can do something like this:
def wrap(f):
def wrapper(*args, **kwargs):
print "args: ", args, kwargs
res = f(*args, **kwargs)
print "result: ", res
return res
return wrapper
This lets me wrap any function regardless of the arguments they take. For instance:
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
Is there a way to do something similar (write a wrapper that can be applied to any function regardless of its input/output types) in Scala?
The decorator design pattern is a structural design pattern. A structural design pattern focuses on the Class and Object composition. The decorator design pattern is all about adding responsibilities to objects dynamically. This pattern also gives some additional responsibility to our base class.
In terms of look and feel, python decorators can be considered similar to Java annotations, but under the hood, they work very very similar to the way Aspects work in Java. python decorators can only be specified on class and function declaration while you may annotate a field using java annotations.
A Decorator is a special type of declaration that can be applied to a function to improve its functionality. This is also a type of metaprogramming.
Decorator is a structural pattern that allows adding new behaviors to objects dynamically by placing them inside special wrapper objects, called decorators. Using decorators you can wrap objects countless number of times since both target objects and decorators follow the same interface.
You could certainly do this with macros. You can convert a method call to a function with partial application:
object Foo {
def bar(i: Int): Int = i + 1
}
val fn = Foo.bar _
defined object Foo
fn: Int => Int = <function1>
Now you have an object, in this case of type Function1[Int, Int]
, which you can pass to a Scala macro, which would be something like this (not tested):
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]
...
}
}
In the body of the macro, you can inspect the whole type signature of fn: A
, which will include the arguments. You can then write code to do your desired side effects, and return a function which you can then invoke. Something like this:
DecoratorMacros.decorate(Foo.bar _)(42)
Macros are fairly involved, but I can elaborate if you think this is a path you'd like to go down.
There is a fundamental issue here: in Scala you have to know what arguments the function should get and actually pass them so that the compiler can be sure that the types match.
Say there is def f(a: List[Int], b: String) = ...
and def g(args: Any*) = f(args)
. This won't compile! (Any*
means any amount of objects with any type). The problem is that Any*
is still only one single argument which actually is translated to one kind of Array
.
Just to make this more clear you could think of an example situation: you have called the wrap(f)
with some function f(a: String, b: String)
. Then you have the output of the wrapper which would somehow accept any amount of any kind of arguments and you make the call wrapper_f(List(1), "a")
. In this situation the wrapper_f(...)
call should be correct but inside the wrapper the wrapped function has a completely different parameter list which can not accept a List[Int]
and a String
. Thus you would get the "Type Error" in runtime which should (in general) be impossible in statically typed programming languages (or at least in Scala).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With