13.2. Principles and syntax

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.