As in Python, and similarly to Java annotations, a decorator is used with a
@ prefix before the function definition. As an example, the decorator
deco1 only prints its name before returning the result unchanged
function deco1 = |fun| {
return |args...| {
return "deco1 + " + fun: invokeWithArguments(args)
}
}It can be used as:
@deco1
function foo = |a| {
return "foo: " + a
}Here, calling println(foo(1)) will print deco1 + foo: 1.
To be the most generic, the function created by a decorator should be a
variable arity function, and thus call the decorated function with
invokeWithArguments, such that it can be applied to any function, regardless
of its arity, as in the previous example.
Indeed, suppose you what to a decorator dec (that does nothing) used like:
@dec function add = |a,b| -> a + b
Such a decorator can be implemented as:
function dec = |func| -> |a, b| -> func(a, b)
But in that case, it will be applicable to two parameters functions only. On the other hand, you cannot do:
function dec = |func| -> |args...| -> func(args)
Indeed, this will throw an exception because func is not a variable arity
function (just a reference on add function) and thus cannot take an array
as parameter. In this case, the decorator have to invoke the original function
like this:
function dec = |func| -> |args...| -> func(args: get(0), args: get(1))
which is equivalent to the first form, but is not generic. The more generic decorator is thus:
function dec = |func| -> |args...| -> func: invokeWithArguments(args)
which can deal with any function.
As illustrated, the decorator is just a wrapper (closure) around the decorated
function. The @ syntax is just syntactic sugar. Indeed, it can also be used
as such:
function bar = |a| -> "bar: " + a
function main = |args| {
println(deco1(^bar)(1))
let decobar = deco1(^bar)
println(decobar(1))
println(deco1(|a| -> "bar: "+a)(1))
}prints all deco1 + bar: 1.
Decorators can also be stacked. For instance:
function deco2 = |fun| {
return |args...| {
return "deco2 + " + fun: invokeWithArguments(args)
}
}
@deco2
@deco1
function baz = |a| -> "baz: " + aprintln(baz(1)) will print deco2 + deco1 + baz: 1
This result can also be achieved by composing decorators, as in:
let deco3 = ^deco1: andThen(^deco2) @deco3 function spam = |a| -> "spam: " + a
Again, println(spam(1)) will print deco2 + deco1 + spam: 1
Moreover, since decorator are just higher order functions, they can be closure on a first argument, i.e. parametrized decorators, as illustrated in the following listing:
module tests.LogDeco
function log = |msg| -> |fun| -> |args...| {
println(msg)
return fun: invokeWithArguments(args)
}
@log("calling foo")
function foo = |a| {
println("foo got a " + a)
}
@log("I'am a bar")
function bar = |a| -> 2*a
function main = |args| {
foo("bar")
println(bar(21))
}will print
calling foo foo got a bar I'am a bar 42
Here, log create a closure on the message, and return the decorator function.
Thus, log("hello") is a function that take a function as parameter, and
return a new function printing the message (hello) before delegating to the
inner function.
Again, since all of this are just functions, you can create shortcuts:
let sayHello = log("Hello")
@sayHello
function baz = -> "Goodbye"A call to println(baz()) will print
Hello Goodbye
The only requirement is that the effective decorator (the expression following
the @) is eventually a HOF returning a closure on the decorated function. As
an example, it can be as elaborated as:
function log = |msgBefore| -> |msgAfter| -> |func| -> |args...| {
println(msgBefore)
let res = func: invokeWithArguments(args)
println(msgAfter)
return res
}
@log("enter foo")("exit foo")
function foo = |a| {
println("foo: " + a)
}where a call foo("bar") will print
enter foo foo: bar exit foo
and with
function logEnterExit = |name| -> log("# enter " + name)("# exit " + name)
@logEnterExit("bar")
function bar = { println("doing something...") }calling bar() will print
# enter bar doing something... # exit bar
or even, without decorator syntax:
function main = |args| {
let strange_use = log("hello")("goodbye")({println(":p")})
strange_use()
log("another")("use")(|a|{println(a)})("strange")
}Let’s now illustrate with some use cases and examples, with a presentation of
some decorators of the standard module
gololang.Decorators.