Kotlin and SAM interface with two parameters

I'm trying to get a SAM with two parameters to work with a Kotlin closure. The best I could do is this:
// Java
public interface Two {
  Integer f(Integer i, String s);
}
// Kotlin
fun f2(two: Two) {
}
fun test() {
  f2 ( Two { i, s -> 42 } )
}
I expected to be able to write:
f2 { i, s -> 42 }
Why do I need to explicitly coerce this expression to `Two`?
4 Likes

SAM-conversions only work for Java methods, not supported for Kotlin functions, because Kotlin has nice functional types, and there's no need for SAM conversion (in your case, I'd convert the lambda inside f2)

That's what I suspected.

I can’t help but feel it’s inconsistent, though: it would be nice if the shortcut form was available everywhere, regardless of the language the function is written in.

1 Like

+1 for this, it's especially odd when working with Java APIs that use SAM interfaces extensively like JavaFX. The "Runnable" here and "Callable" there is redundant and doesn't add readability - the compiler should be able to figure this out.

3 Likes

another +1 to this, because without it I’m going to have to reinvent some idioms I have around guice.

In java, if a class has non-guice-time dependency, we use AssistedInject, so one example of such a class might be

class CoolService{
  public @FunctionalInterface Factory{
    CoolService create(int importantValue)
  }

  @Inject
  CoolService(Component1 comp, @Assisted int importantValue){ 
    //...
  }
}

for consumers of CoolService (that is to say components that depend on it, typically controllers of one sort or another), they would inject this factory:

class DependsOnCoolService{
  DependsOnCoolService(CoolServiceFactory svcFactory){
    //...
  }
}

this is nice from a testing environment, since we can stub that factory out nicely with a mock or stub or something.

class DependsOnCoolServiceTest{
  @Test doSomething(){
    //setup
    DependsOnCoolService componentUnderTest = new DependsOnCoolService(
        x -> mock(CoolService.class)
    );

    //... 
  }
}

The actual plumbing at runtime code is to configure guice to use the factory to create a DependsOnCoolService:

Guice.createInjector(binder -> {
  binder.install(new FactoryModuleBinding().build(CoolService.Factory.class));
  //...

notice we explicitly leverage javas SAM to convert the lambda x -> mock(... to a CoolService.Factory.

The same idiom now doesn’t work in kotlin. So what can we do instead?

  • The equivalent CoolService is now a kotlin object,
  • The DependsOnCoolService is a kotlin object
  • The DependsOnCoolServiceTest is a kotlin object
  • The guice plumbing is sill done in java, though thats easy enough to port.

so, whats our new idiom? I can go back to anonymous nested class, but that’s no fun at all, and I’m drawing a blank on an alternative.

Instead of taking a CoolServiceFactory we could ‘lift’ that to a svcFactory : () -> CoolService, which places the burden on the guice configuration… which I just attempted, and unfortunately theres something between kotlins implied contravariance and guice that dont agree with each other.

Guice.createInjector(object : Module{
    override fun configure(binder: Binder) {
        binder.install(FactoryModuleBuilder()
                .implement(OPYLConfigurationApplier::class.java, OPYLConfigurationApplier::class.java)
                .build(object : TypeLiteral<kotlin.jvm.functions.Function0<CoolService>>(){})
        )
    }
})

//throws 
com.google.inject.CreationException: Unable to create injector, see the following errors:

1) An exception was caught and reported. Message: Expected a Class, ParameterizedType, or GenericArrayType, but <? extends com.my_company.CoolService> is of type com.google.inject.internal.MoreTypes$WildcardTypeImpl
  at com.empowerops.api.OPYLImporterFixture$canGuice$1.configure(OPYLImporterFixture.kt:25) (via modules: com.empowerops.api.OPYLImporterFixture$canGuice$1 -> com.google.inject.assistedinject.FactoryModuleBuilder$1)

1 error

would really like SAM conversion in kotlin

+1
Kotlin must be Java fully interoperable and functional interfaces is a Java 8 feature.

Define an inline object is an ugly workaround, Java does it better.

1 Like

You both will be so happy :wink: https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions

Also note that this feature works only for Java interop

A “Java interoperable” Kotlin library should use Kotlin Function0 or Java Supplier?

Ahh, so I’m actually requesting a kotlin native SAM conversion, not just Java conversion:

@Test fun when_binding_things(){
    //setup
    val injector = Guice.createInjector(object : AbstractModule() {
        override fun configure() {
            install(FactoryModuleBuilder().build(KotlinClassWithSAMFactory.Factory::class.java))
        }
    })

    val guiceInstance : KotlinClassWithSAMFactory.Factory = injector.getInstance(KotlinClassWithSAMFactory.Factory::class.java)
    val lambdaInstance : KotlinClassWithSAMFactory.Factory = { value : Int -> KotlinClassWithSAMFactory(value) }
    //error: cannot convert value:Int -> KCWSAMF to KCWSAMF.F

    assertThat(guiceInstance)
            .isNotNull()
            .isInstanceOf(KotlinClassWithSAMFactory.Factory::class.java)

    assertThat(lambdaInstance)
            .isNotNull()
            .isInstanceOf(KotlinClassWithSAMFactory.Factory::class.java)
}

class KotlinClassWithSAMFactory @Inject constructor(@Assisted val importantValue : Int){

    @FunctionalInterface interface Factory {
        fun create(important : Int) : KotlinClassWithSAMFactory
    }
}

:frowning:

Kotlin support for JDK 7/8 don’t use the “nice functional types”, there is an example:

Can you justify this “bad design”? Why use SAM interface and avoid functional types?

Thanks

PS: “it works” or “special case” aren’t -in my opinion- a enough good motivations for an absolute constraint.

Not sure why you’re calling this “bad design”. No design was involved here. The kotlinx.support library does not define any APIs; it simply exposes the APIs defined in JDK 7 and 8, which, unsurprisingly, use the JDK SAM interfaces and not the Kotlin functional types.

1 Like

It seems a “special case” :frowning:

You can expose same API to Kotlin in “Kotlin way”, right?
Therefore -in my opinion- there is a design choice.

I think that actual design is a good choice but an fully interoperable language can’t discriminate “Java way” vs “Kotlin way”.

This is a “Java library case”, it isn’t a special case; a Java/Kotlin library is full of this cases, may I write it in Kotlin only without penalties?

Please reconsider your opinion, thanks.

No, we can’t expose it in a different way. The JDK method accepts an instance of Supplier, so we need to pass an instance of Supplier to it.

And we do so indeed.
Look for example at MutableCollection.removeIf: overloads for both Predicate<in T> and (T) -> Boolean are provided.

Thanks,
honestly.

But I disagree, doubling the code seems like a workaround.
You can found a solution to “verbose way”, in the next release, in further releases.
This isn’t a key feature but -in my opinion- the current solution requires more considerations, like
http://www.artima.com/intv/simplexity2.html

Thanks for your consideration,
these feedback are very appreciated.
Vasco

This is a consequence of “Java way” and “Kotlin way” distinction when programming a shared interface for Java and Kotlin.

    interface TaskMeter {
        fun measure(runnable: Runnable) : Long
        
        fun measure(function: () -> Unit) : Long

        // and so on...
        fun measure(runnables: Iterable<Runnable>) : Long
        
        fun measure(functions: Sequence<() -> Unit>) : Long
    }

Hard to implement and use both in Java and Kotlin.
Is this the right way for a pragmatic, concise, interoperable language?

Sorry, you seem to be putting forward a lot of principle-based arguments, but I fail to see what is the actual problem that you want us to solve.

In your TaskMeter example, there is absolutely no need to duplicate the method declarations. Java SAM conversions work just fine with Kotlin function types, so the second declaration for measure() works equally well for Java and for Kotlin users and implementers.

For the second example, the semantics of the Sequence interface (lazily evaluated, potentially infinite sequence) is simply not appropriate for the task, so it’s much better to use Iterable, regardless of any Java/Kotlin interop concerns.

1 Like

Hi yole,
I am sorry but I disagree again.
I think that the right Java interoperable interface is

        interface TaskMeter {
            fun measure(runnable: Runnable) : Long
            
            fun measure(runnables: Iterable<Runnable>) : Long
        }

I have no reason to expose “in a public shared interface”(*) any detail about Kotlin/Scala/Groovy implementation, otherwise I am free to swith from Java to Kotlin but I can’t throw away Kotlin later.

Moreover I think that every language must avoid code duplication as much as possible; Kotlin JDK support is a common case, the common solution in an adapter (i provide interface A, you require interface B), this adapter can be create automatically at compile time and, in my opinion, Kotlin compiler may do it.

Kotlin declares Java compatibility, not vice versa.

I’m sorry for my poor english, I think to have some difficult to explain my ideas.
I wish this is an usefull exchange, not a dispute.

Thanks for your patience,
Vasco

(*) TaskMeter can by a legacy interface translated in Kotlin

+1 as more java 8 libraries arise, SAM’s like Function, Consumer are everywher in java libs. For polyglott projects, one would like Kotlin’s type inference to recognize SAM’s. I know type inference of SAM’s works for methods defined in “real” java classes, however it fails when defined inside a kotrlin class.

e.g. a method
fun foo(Consumer<Bar> c)

has to be called from kotlin like this
obj.foo( Consumer { bar -> ... })

vs. java8
obj.foo( bar -> ... ).

IMO kotlin type inference should enable
obj.foo { bar -> ...}

2 Likes

This seems to mean that using auto-conversion, any code that used a custom interface and lambda expression in Java 8, must be converted manually to be inline object as object: MyInterface { override fun blah() { ... } } (like in pre-Java 8 times and anonymous interface implementation)

1 Like