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)
}
}
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
By: Stephan Schmidt on February 2, 2010
at 4:38 pm