Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python style decorator in Scala

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?

like image 817
Jules Olléon Avatar asked Mar 27 '15 22:03

Jules Olléon


People also ask

What is decorator 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.

Is Java annotation same as Python decorator?

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.

Is decorator metaprogramming?

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.

Is Python decorator a decorator pattern?

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.


2 Answers

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.

like image 81
Richard Close Avatar answered Oct 12 '22 14:10

Richard Close


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).

like image 30
Matias Saarinen Avatar answered Oct 12 '22 14:10

Matias Saarinen