[
Date Prev][
Date Next][
Thread Prev][
Thread Next][
Date Index][
Thread Index]
[
List Home]
[golo-dev] Augmentation scope
|
Hi all,
as recently noted by Philippe, the scope of augmentations can be quite
misleading.
Indeed, an augmentation (classical `augment` or named `augment ... with`)
are only visible in the module where it is defined, or if the said
module is imported (see
http://golo-lang.org/documentation/next/#_augmentation_scopes_reusable_augmentations)
However, while this behavior can be useful to limit the scope of
augmentation and thus have more predictable code (explicit is better
than implicit), it prevents the creation of libraries of polymorphic
functions or reusable mixin-like augmentations.
For instance, if I want to create a library of functions dealing with
sized object:
module MyLib
function printSize = |o| {
println("The size of " + o + " is " + o: size() + "!")
}
This function can only be used on object having a “native” `size`
method. For example, given the following module
module MyData
struct Sized = { size }
struct NoSize = { val }
augment NoSize {
function size = |this| -> this: val()
}
the `printSize` function has a somewhat unpredictable behavior if I
don't know the underlying implementation of my objects, which is bad for
code encapsulation:
module Main
import MyData
import MyLib
augment java.lang.String {
function size = |this| -> this: length()
}
function main = |args| {
let str = "hello"
let s = Sized(42)
let ns = NoSize(42)
let l = list[1, 2, 3]
let do = DynamicObject(): define("size", |this| -> 42)
# Ok, it's a native java method
require(l: size() == 3, "err")
try {
printSize(l)
} catch (e) {
println("## ERR: " + e)
}
# Ok, it's a generated java method
require(s: size() == 42, "err")
try {
printSize(s)
} catch (e) {
println("## ERR: " + e)
}
# Ok, DynamicObject
require(do: size() == 42, "err")
try {
printSize(do)
} catch (e) {
println("## ERR: " + e)
}
# Ok, augmented in the module where it is used
require(str: size() == 5, "err")
# Fails... MyLib don't know the augmentation
try {
printSize(str)
} catch (e) {
println("## ERR: " + e)
}
# Ok, the module where the struct is augmented is imported
# where the method is used
require(ns: size() == 42, "err")
# Fails... MyLib don't know the augmentation
try {
printSize(ns)
} catch (e) {
println("## ERR: " + e)
}
}
running this module, we got:
The size of [1, 2, 3] is 3!
The size of struct Sized{size=42} is 42!
The size of gololang.DynamicObject@59a6e353 is 42!
## ERR: java.lang.NoSuchMethodError: class java.lang.String::size
## ERR: java.lang.NoSuchMethodError: class MyData.types.NoSize::size
while all `require` calls are ok, since everything needed is imported,
calls to `printSize` can fail.
While I'm not sure how to change this, I think it's unfortunate.
I think of 2 solutions:
* make augmentation applications be looked-up in all the modules of the
call chain (not only where the function is defined but also where it
is used!). This can be harder than it seems and looks like ES hoisting :)
* add a `augment globally` construct (or `global augment`) that allow
the augmentation to be taken into account anywhere in the code. I
don't think it's a good approach either, since it can make realy hard
to know which methods are available on an object, and since import
order is important to know which augmentation is taken into account,
the behavior can be unpredictable in the case of conflicting
augmentations.
What do you think ?