Posted by: paololosi | January 25, 2010

Python decorators in Scala

Are you looking for Python decorators in Scala? I did look for them when
first studying Scala. I tried to look up for Scala/Java annotations,
as the syntax was too similar to ignore the clue.
But annotations are for adding runtime (reflection accessible) metadata.

As it turned out, Scala usually try to push as much as possible out of language specification to library implementation, and, also in this
case, there are more general, powerful language features to mimic Python
decorators.

Simple Python decorators

Let’s consider the first naive example:

def three_times(wrapped):
    def wrapping(*args, **kargs):
        for _ in xrange(3):
            wrapped(*args, **kargs)
    return wrapping

@three_times
def foo():
   print "hello!"

this yields:

>>> foo()
hello!
hello!
hello!

In scala you can leverage call by name evaluation strategy to obtain a similar result. Instead of wrapping the entire function, you wrap the function body (or only part of it, when necessary)

scala> def threeTimes(code: =>Unit) = 1 to 3 foreach {_ => code}
threeTimes: (code: => Unit)Unit

scala> def foo = threeTimes { println("hello!") }
foo: Unit

scala> foo
hello!
hello!
hello!

In the more general case, call by name is very effective in building customized and powerful control structures (see for the scala actors library
for an example of how scala can be “extended” with specialized control structure).

Also Python’s with statement can be implement using call-by-name.

Decorating recursive functions

There is another way to “decorate” a function, that is useful when
decorating a recursive function. Let’s take the classical Fibonacci function:

def memo(wrapped):
    cache = {}
    def wrapping(*args):
        if args in cache:
            print "cache hit for %s" % args
            return cache[args]
        else:
            ret = wrapped(*args)
            cache[args] = ret
            return ret
    return wrapping

@memo
def fib(i):
    return ( 0 if i == 0 else
             1 if i == 1 else
             fib(i-1) + fib(i-2) )

Note that the call-by-name approach doesn’t work in this case because the memoizing function needs to access memoized function parameters.

Using Scala’s function literals we can do:

object Test {

  def memo(f: Int=>Int) = new Function1[Int,Int] {

    var cache = Map[Int,Int]()

    def apply(i:Int) = if (cache contains i) {
                           println("cache hit for "+i)
                           cache(i)
                       } else {
                           val ret = f(i)
                           cache += (i -> ret)
                           ret
                       }
  }

  val fib: Int=>Int = memo {
    case 0 => 0
    case 1 => 1
    case n => fib(n-1) + fib(n-2)
  }

}


Responses

  1. Very nice.

    To more reflect the Python code perhaps one would use

    def memo(f: A=>B)

    to make ti as reusable as the Python decorator.

    Cheers
    Stephan
    http://codemonkeyism.com


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.