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: " + a
println(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
.