[
Date Prev][
Date Next][
Thread Prev][
Thread Next][
Date Index][
Thread Index]
[
List Home]
[golo-dev] Issue on overloaded function resolution
|
# Issue on overloaded function resolution
This message is a synthesis of comments on PRs
[#312](https://github.com/eclipse/golo-lang/pull/312)
and [#313](https://github.com/eclipse/golo-lang/pull/313).
We have an issue regarding the resolution of overloaded functions, more
specifically when using variable arity ones (varargs)
## A little history
When using the literal notation for function references
(`^module::function`) or the `Predefined::fun` function without
specifying the function arity (`fun("function", module.class)` or
`fun("function", module.class, -1)`), for which the literal notation is
just syntactic sugar, if the referenced function was overloaded, that is
the function has multiple definitions with different arities, the return
function reference used to be undefined. The first found was return, but
with no guaranty on with this first one would be.
For instance, with code like
function foo = |x| -> ...
function foo = |x, y| -> ...
let f = fun("foo", currentModule.class)
`f` could be either of the two functions.
A correction was applied to raise
a `AmbiguousFunctionReferenceException` in this case, to avoid guessing.
This force the developer to be explicit on the required arity by using
`fun("foo", currentModule.class, 1)` or `fun("foo", currentModule.class,
2)`, instead of silently using one or the other.
The problem is not present when calling the function directly as in
`foo(1)` or `foo(1, 2)`, since in this case, the number of arguments is
known. This resolution takes place in
`runtime.FunctionCallSupport::findStaticMethodOrField`.
As a side note, their is some logic duplication between
`runtime.FunctionCallSupport::findStaticMethodOrField` and
`Predefined::fun` (and probably also in the method call resolution).
## Current problem
However, the direct call resolution use the number of *arguments* used
on the call site, and try to match them with the number of *parameters*
of the candidate function.
This raise an issue when the function is overloaded with a variable
arity that shadow the fixed one. For instance, given the functions:
function foo = |x| -> ...
function foo = |x, y| -> ...
function foo = |xs...| -> ...
* when using `fun`, the third one shadows the first one, and thus
`fun("foo", mod.class, 1)` throws an exception;
* when doing a direct call, the third one shadows the two others,
depending on the number of arguments given: `foo(1)` can call the
first or the third, and `fun(1, 2)` can call the second or the third.
## Some tests
See the two included files. This gave me:
$ ./test.sh 1000
379 direct 0: 0
621 direct 0: 1v
741 direct 1: 1
151 direct 1: 1v
108 direct 1: 2v
93 direct 2: 1v
799 direct 2: 2
108 direct 2: 2v
784 direct 3: 1v
216 direct 3: 2v
1000 ref 0: 0
1000 ref -1: failed with AmbiguousFunctionReferenceException
1000 ref 1: failed with AmbiguousFunctionReferenceException
1000 ref 2: failed with AmbiguousFunctionReferenceException
1000 # run
This is not just a corner case!
## Solutions
Note that the two issues, though related, are not exactly the same, and
thus a different solution can be adopted for direct call and function
reference.
### Explicit specification
Add a parameter to `fun` to specify if we want variable or fixed arity.
This would work when getting a function reference, but how do we resolve
direct calls?
### Multiple references proxy
Also only in the case of function references (e.g. `fun` with no arity),
we could return a proxy containing all the ambiguous references instead
of throwing a runtime exception. The resolution can then be done on the
call site, in a manner similar to direct calls.
This does not solve the issue of ambiguous direct calls.
### Implicit choice
Define a priority between functions, for instance when multiple choices
among fixed and variable arity, always use the fixed one.
Optionally also define a priority when referencing with `fun` without
arity instead of raising a runtime exception.
I’m not found of this solution. Their will surely be cases where the
wrong function is used, so we need a way to be more explicit.
### Runtime exception
As for the `fun` function, throw an `AmbiguousFunctionCallException` in
`FunctionCallSupport` if the function is not fully defined. This does
not really solve the problem, but at least it does not pass unnoticed.
### Compile time exception
Refuse to guess, and throw a compilation exception if the developer has
defined ambiguous functions, where a variable arity one can shadow
a fixed one, forcing the developer to rethink its API.
module Test
function foo = -> "0"
function foo = |x| -> "1"
function foo = |x, y| -> "2"
function foo = |x...| -> "1v"
function foo = |x, y...| -> "2v"
function test = |msg, fn| {
try {
println(msg + fn())
} catch (e) {
println(msg + "failed with " + e: getClass(): getName())
}
}
function main = |args| {
println("# run")
# can be 0 or 1v
test("direct 0: ", -> foo())
# can be 1 or 1v or 2v
test("direct 1: ", -> foo(1))
# can be 2 or 1v or 2v
test("direct 2: ", -> foo(1, 2))
# can be 1v or 2v
test("direct 3: ", -> foo(1, 2, 3))
# ok, always 0
test("ref 0: ", -> fun("foo", Test.class, 0)())
# can be 1 or 1v
test("ref -1: ", -> fun("foo", Test.class, -1)(1))
# can be 1 or 1v
test("ref 1: ", -> fun("foo", Test.class, 1)(1))
# can be 2 or 2v
test("ref 2: ", -> fun("foo", Test.class, 2)(1, 2))
}
Attachment:
test.sh
Description: Bourne shell script
Attachment:
signature.asc
Description: PGP signature