You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2020/05/21 12:00:03 UTC

[groovy] branch GROOVY_2_5_X updated (3de88f7 -> 6f31bf4)

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a change to branch GROOVY_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git.


    from 3de88f7  Bump version on GROOVY_2_5_X branch
     new e4e716f  initial cut of doco for dynamic method selection
     new 6f31bf4  additional doco for dynamic method selection

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/spec/doc/core-object-orientation.adoc          | 115 ++++++++++++++++++++-
 src/spec/test/objectorientation/MethodsTest.groovy | 102 ++++++++++++++++++
 2 files changed, 216 insertions(+), 1 deletion(-)


[groovy] 01/02: initial cut of doco for dynamic method selection

Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch GROOVY_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit e4e716f3de6200bba48937b0fb0fee7b41897d53
Author: Paul King <pa...@asert.com.au>
AuthorDate: Mon May 18 19:42:45 2020 +1000

    initial cut of doco for dynamic method selection
---
 src/spec/doc/core-object-orientation.adoc          | 108 ++++++++++++++++++++-
 src/spec/test/objectorientation/MethodsTest.groovy |  92 ++++++++++++++++++
 2 files changed, 199 insertions(+), 1 deletion(-)

diff --git a/src/spec/doc/core-object-orientation.adoc b/src/spec/doc/core-object-orientation.adoc
index 0ff7be4..8961ae6 100644
--- a/src/spec/doc/core-object-orientation.adoc
+++ b/src/spec/doc/core-object-orientation.adoc
@@ -432,7 +432,113 @@ include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=va
 
 ==== Method selection algorithm
 
-(TBD)
+Dynamic Groovy supports https://en.wikipedia.org/wiki/Multiple_dispatch[multiple dispatch] (aka multimethods).
+When calling a method, the actual method invoked is determined
+dynamically based on the run-time type of methods arguments.
+First the method name and number of arguments will be considered (including allowance for varargs),
+and then the type of each argument.
+Consider the following method definitions:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=multi_methods,indent=0]
+----
+
+Perhaps as expected, calling `method` with `String` and `Integer` parameters,
+invokes our third method definition.
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=call_single_method,indent=0]
+----
+
+Of more interest here is when the types are not known at compile time.
+Perhaps the arguments are declared to be of type `Object` (a list of such objects in our case).
+Java would determine that the `method(Object, Object)` variant would be selected in all
+cases (unless casts were used) but as can be seen in the following example, Groovy uses the runtime type
+and will invoke each of our methods once (and normally, no casting is needed):
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=call_multi_methods,indent=0]
+----
+
+For each of the first two of our three method invocations an exact match of argument types was found.
+For the third invocation, an exact match of `method(Integer, Integer)` wasn't found but `method(Object, Object)`
+is still valid and will be selected.
+
+Method selection then is about finding the _closest fit_ from valid method candidates which have compatible
+parameter types.
+So, `method(Object, Object)` is also valid for the first two invocations but is not as close a match
+as the variants where types exactly match.
+To determine the closest fit, the runtime has a notion of the _distance_ an actual argument
+type is away from the declared parameter type and tries to minimise the total distance across all parameters.
+
+The following table illustrates some factors which affect the distance calculation.
+
+[cols="1,1" options="header"]
+|====
+| Aspect
+| Example
+
+| Directly implemented interfaces match more closely than ones from further up the inheritance hierarchy.
+a| Given these interface and method definitions:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=multi_method_distance_interfaces,indent=0]
+----
+
+The directly implemented interface will match:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=multi_method_distance_interfaces_usage,indent=0]
+----
+
+| An Object array is preferred over an Object.
+a|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=object_array_over_object,indent=0]
+----
+
+| Non-vararg variants are favored over vararg variants.
+a|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=non_varargs_over_vararg,indent=0]
+----
+
+| If two vararg variants are applicable, the one which uses the minimum number of vararg arguments is preferred.
+a|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=minimal_varargs,indent=0]
+----
+
+| Interfaces are preferred over super classes.
+a|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=multi_method_distance_interface_over_super,indent=0]
+----
+|====
+
+In the case where two variants have exactly the same distance, this is deemed ambiguous and will cause a runtime exception:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=multi_method_ambiguous,indent=0]
+----
+
+Casting can be used to select the desired method:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=multi_method_ambiguous_cast,indent=0]
+----
+
 
 ==== Exception declaration
 
diff --git a/src/spec/test/objectorientation/MethodsTest.groovy b/src/spec/test/objectorientation/MethodsTest.groovy
index e89d263..0223e76 100644
--- a/src/spec/test/objectorientation/MethodsTest.groovy
+++ b/src/spec/test/objectorientation/MethodsTest.groovy
@@ -160,4 +160,96 @@ class MethodsTest extends GroovyTestCase {
         '''
     }
 
+    void testMultiMethods() {
+        assertScript '''
+            // tag::multi_methods[]
+            def method(Object o1, Object o2) { 'o/o' }
+            def method(Integer i, String  s) { 'i/s' }
+            def method(String  s, Integer i) { 's/i' }
+            // end::multi_methods[]
+
+            // tag::call_single_method[]
+            assert method('foo', 42) == 's/i'
+            // end::call_single_method[]
+
+            // tag::call_multi_methods[]
+            List<List<Object>> pairs = [['foo', 1], [2, 'bar'], [3, 4]]
+            assert pairs.collect { a, b -> method(a, b) } == ['s/i', 'i/s', 'o/o']
+            // end::call_multi_methods[]
+        '''
+
+        assertScript '''
+            import static groovy.test.GroovyAssert.shouldFail
+            // tag::multi_method_ambiguous[]
+            def method(Date d, Object o) { 'd/o' }
+            def method(Object o, String s) { 'o/s' }
+
+            def ex = shouldFail {
+                println method(new Date(), 'baz')
+            }
+            assert ex.message.contains('Ambiguous method overloading')
+            // end::multi_method_ambiguous[]
+            // tag::multi_method_ambiguous_cast[]
+            assert method(new Date(), (Object)'baz') == 'd/o'
+            assert method((Object)new Date(), 'baz') == 'o/s'
+            // end::multi_method_ambiguous_cast[]
+        '''
+
+        assertScript '''
+            // tag::multi_method_distance_interfaces[]
+            interface I1 {}
+            interface I2 extends I1 {}
+            interface I3 {}
+            class Clazz implements I3, I2 {}
+
+            def method(I1 i1) { 'I1' }
+            def method(I3 i3) { 'I3' }
+            // end::multi_method_distance_interfaces[]
+
+            // tag::multi_method_distance_interfaces_usage[]
+            assert method(new Clazz()) == 'I3'
+            // end::multi_method_distance_interfaces_usage[]
+        '''
+
+        assertScript '''
+            // tag::non_varargs_over_vararg[]
+            def method(String s, Object... vargs) { 'vararg' }
+            def method(String s) { 'non-vararg' }
+
+            assert method('foo') == 'non-vararg'
+            // end::non_varargs_over_vararg[]
+        '''
+
+        assertScript '''
+            // tag::minimal_varargs[]
+            def method(String s, Object... vargs) { 'two vargs' }
+            def method(String s, Integer i, Object... vargs) { 'one varg' }
+
+            assert method('foo', 35, new Date()) == 'one varg'
+            // end::minimal_varargs[]
+        '''
+
+        assertScript '''
+            // tag::object_array_over_object[]
+            def method(Object[] arg) { 'array' }
+            def method(Object arg) { 'object' }
+
+            assert method([] as Object[]) == 'array'
+            // end::object_array_over_object[]
+        '''
+
+        assertScript '''
+            // tag::multi_method_distance_interface_over_super[]
+            interface I {}
+            class Base {}
+            class Child extends Base implements I {}
+
+            def method(Base b) { 'superclass' }
+            def method(I i) { 'interface' }
+
+            assert method(new Child()) == 'interface'
+            // end::multi_method_distance_interface_over_super[]
+        '''
+    }
+
 }


[groovy] 02/02: additional doco for dynamic method selection

Posted by pa...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch GROOVY_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 6f31bf4560be637e5958db8733430f0e88791bc3
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu May 21 16:44:28 2020 +1000

    additional doco for dynamic method selection
---
 src/spec/doc/core-object-orientation.adoc          |  7 +++++++
 src/spec/test/objectorientation/MethodsTest.groovy | 10 ++++++++++
 2 files changed, 17 insertions(+)

diff --git a/src/spec/doc/core-object-orientation.adoc b/src/spec/doc/core-object-orientation.adoc
index 8961ae6..65a3455 100644
--- a/src/spec/doc/core-object-orientation.adoc
+++ b/src/spec/doc/core-object-orientation.adoc
@@ -523,6 +523,13 @@ a|
 ----
 include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=multi_method_distance_interface_over_super,indent=0]
 ----
+
+| For a primitive argument type, a declared parameter type which is the same or slightly larger is preferred.
+a|
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/objectorientation/MethodsTest.groovy[tags=primitive_larger_over_smaller,indent=0]
+----
 |====
 
 In the case where two variants have exactly the same distance, this is deemed ambiguous and will cause a runtime exception:
diff --git a/src/spec/test/objectorientation/MethodsTest.groovy b/src/spec/test/objectorientation/MethodsTest.groovy
index 0223e76..2f4a691 100644
--- a/src/spec/test/objectorientation/MethodsTest.groovy
+++ b/src/spec/test/objectorientation/MethodsTest.groovy
@@ -239,6 +239,16 @@ class MethodsTest extends GroovyTestCase {
         '''
 
         assertScript '''
+            // tag::primitive_larger_over_smaller[]
+            def method(Long l) { 'Long' }
+            def method(Short s) { 'Short' }
+            def method(BigInteger bi) { 'BigInteger' }
+
+            assert method(35) == 'Long'
+            // end::primitive_larger_over_smaller[]
+        '''
+
+        assertScript '''
             // tag::multi_method_distance_interface_over_super[]
             interface I {}
             class Base {}