GEP-12

Author:


Metadata
Number

GEP-12

Title

SAM coercion

Version

4

Type

Feature

Status

Final

Leader

Jochen "blackdrag" Theodorou

Created

2013-05-30

Last modification 

2018-10-29

Abstract: SAM coercion

SAM stands for Single Abstract Method. A SAM type is an abstract class or interface with a single abstract method. SAM coercion involves transforming a groovy.lang.Closure instance into an object suitable for our SAM type. The coercion can happen as part of an assignment or as the result of a method call. Since this transformation might be outside of the types provided by Closure itself, it can be more than a simple Java style cast. Closure becomes a kind of sub type to all SAM types. Groovy has other such transformations without explicit cast or asType usage, which are number object transformations as well as the conversion of GString to String.

Motivation

Even before Java8 we had discussions about supporting different interfaces with Closure like Runnable and Callable. These two being easy cases, any framework can define a myriad of interfaces and abstract classes. This then requires to "groovify" the library by writing a helper layer capable of transforming Closure objects into something the library then understand. While it is unlikely of this approach to make Groovy Builder surplus, it can still help with a more simple integration.

Meaning of "Single Abstract Method"

For a SAM type to be a SAM type according to this GEP an abstract class or interface with a single abstract method is required. Any static methods, as well as non-abstract methods are not counted. The abstract method may be defined in the SAM class itself, but it can also be a parent. The required visibility modifier for the method is public though.

SAM examples:

  • Simple Interface:

interface SAM {
  def foo()
}
  • Interface with defender method (aka virtual extension method):

interface SAM {
  def foo()
  def bar() default { 1 }
}
  • Interface inheriting from another interface:

interface ParentOfSAM {}
interface SAM extends ParentOfSAM {
  def foo()
}
  • Interface inheriting from another interface, but not defining a method on its own:

interface ParentOfSAM {
  def foo()
}
interface SAM extends ParentOfSAM {}
  • simple abstract class

abstract class SAM {
  abstract foo()
}
  • abstract class with a abstract and a non abstract method:

abstract class SAM {
  abstract foo()
  def bar() { 1 }
}
  • abstract class extending other class:

abstract class ParentOfSAM1 {
  abstract foo()
}
  • abstract class SAM1 extends ParentOfSAM1 {}

class ParentOfSAM {
   def bar() { 1 }
}
abstract class SAM2 extends  {
  abstract foo()
}
  • abstract class implementing interface:

interface SomeInterface{
  def foo()
}
abstract class SAM1 implements SomeInterface {}
abstract class SAM2 implements Runnable{}
interface AnotherInterface {}
abstract class SAM3 implements AnotherInterface {
  abstract foo()
}

Non-SAM examples:

  • empty interface:

interface SomeMarker {}
  • interface with two methods:

interface TwoMethods {
  def foo()
  def bar()
}
  • abstract class with two abstract methods:

abstract class TwoMethods {
  abstract foo()
  abstract bar()
}
  • empty abstract class:

abstract class Empty {}

Influence on method selection

The normal method selection algorithm tries to find the most specific method to the given argument runtime types and the most general for null. Since a SAM type and a target method parameter type are not in an inheritance relation "most specific" needs a redefinition in parts. It will be assumed the SAM type is like a direct child of the given target type, but if the SAM type is one implemented by Closure (Runnable and Callable), then no SAM coercion will be needed. This case is preferred in method selection. In case of an overloaded method, where each can be used as target for the SAM coercion, method selection will thus fail, regardless their internal relation. In Groovy the actual method signature of the SAM type and the coercion target are not important. Also it is not important if the target type is an abstract class or an interface.

Example of two SAM targets with failing runtime method selection:

interface SAM1 { def foo(String s)}
interface SAM2 { def bar(Integer i)}
def method(x, SAM1 s1){s1.foo(x)}
def method(x, SAM2 s2){s2.bar(x)}
method (1)   {it}  // fails because SAM1 and SAM2 are seen as equal
method ("1") {it}  // fails because SAM1 and SAM2 are seen as equal

Example of SAM type being ignore as a non-coercion case is available:

interface SAM {def foo(String s)}
def method(SAM s) {1}
def method(Runnable r) {2}
assert method {it} == 2

Influence on static typing system

The Scope for the static type system is split into a basic part for Groovy 2.2 and an extended one for a later version (2.3 or 3.0)

Groovy 2.2 static checks

The type checking in Groovy 2.2 will be limited to mimic the behavior of normal Groovy. No method signature checks are performed, as well as there will be no additional test or method selection based on the type provided by the open block.

Groovy 2.2+ static checks

In later versions of Groovy the static type checker has to be improved to refine method selection by the given type signature through the open block or lambda. A SAM type is then a fitting type for the coercion only if the provided types and the target types in the SAM are matching by number and type itself. A more detailed description can be found here: http://cr.openjdk.java.net/~dlsmith/jsr335-0.6.1/F.html

Reference implementation

Update history

3 (2013-07-01)

Version as extracted from Codehaus wiki

4 (2018-10-29)

Numerous minor tweaks