Many dynamic languages support the ability to extend existing classes by adding new methods to them. You may think of categories in Objective-C and Groovy, or open classes in Ruby.
This is generally implemented by providing meta-classes. When some piece of code adds a method
foo
to, say, SomeClass
, then all instances of SomeClass
get that new foo
method. While very
convenient, such an open system may lead to well-known conflicts between the added methods.
Golo provides a more limited but explicit way to add methods to existing classes in the form of class augmentations.
Let us motivate the value of augmentations by starting with the following example. Suppose that we would like a function to wrap a string with a left and right string. We could do that in Golo as follows:
function wrap = |left, str, right| -> left + str + right # (...) let str = wrap("(", "foo", ")") println(str) # prints "(abc)"
Defining functions for such tasks makes perfect sense, but what if we could just add the wrap
method to all instances of java.lang.String
instead?
Defining an augmentation is a matter of adding a augment
block in a module:
module foo augment java.lang.String { function wrap = |this, left, right| -> left + this + right } function wrapped = -> "abc": wrap("(", ")")
More specifically:
augment
definition is made on a fully-qualified class name, and
It is a good convention to name the receiver this
, but you are free to call it differently.
Also, augmentation functions can take variable-arity arguments, as in:
augment java.lang.String { function concatWith = |this, args...| { var result = this foreach(arg in args) { result = result + arg } return result } } # (...) function varargs = -> "a": concatWith("b", "c", "d")
It should be noted that augmentations work with class hierarchies too. The following example adds an
augmentation to java.util.Collection
, which also adds it to concrete subclasses such as java.util.LinkedList
:
augment java.util.Collection { function plop = |this| -> "plop!" } # (...) function plop_in_a_list = -> java.util.LinkedList(): plop()
By default, an augmentation is only visible from its defining module.
Augmentations are clear and explicit as they only affect the instances from which you have decided to make them visible.
It is advised to place reusable augmentations in separate module definitions. Then, a module that needs such augmentations can make them available through imports.
Suppose that you want to define augmentations for dealing with URLs from strings. You could define a
string-url-augmentations.golo
module source as follows:
module my.StringUrlAugmentations import java.net augment java.lang.String { function toURL = |this| -> URL(this) function httpGet = |this| { # Open the URL, get a connection, grab the body as a string, etc # (...) } # (...) }
Then, a module willing to take advantage of those augmentations can simply import their defining module:
module my.App import my.StringUrlAugmentations function googPageBody = -> "http://www.google.com/": httpGet()
As a matter of style, we suggest that your module names end with Augmentations
. Because importing a
module imports all of its augmentation definitions, we suggest that you modularize them with fine
taste (for what it means).
Golo comes with a set of pre-defined augmentations over collections, strings, closures and more.
These augmentation do not require a special import, and they are defined in the
gololang.StandardAugmentations
module.
Here is an example:
let odd = [1, 2, 3, 4, 5]: filter(|n| -> (n % 2) == 0) let m = map[] println(m: getOrElse("foo", -> "bar"))
The full set of standard augmentations is documented in the generated golodoc (hint: look for
doc/golodoc
in the Golo distribution).