Skip to main content

[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


Back to the top