Yarikx/reductor

@AutoReducer & generics in Kotlin

jhavatar opened this issue · 6 comments

When using Kotlin, My simple todo list reducer fails to build/generate its implementation with the following error:

error: First parameter arg0 of method addItem(java.util.List<io.chthonic.template.kotlin.data.model.TodoItem>,io.chthonic.template.kotlin.data.model.TodoItem) should have the same type as state (java.util.List<? extends io.chthonic.template.kotlin.data.model.TodoItem>)
1 error

The reducer:

    @AutoReducer.Action(
            value = TodoListActions.ADD_ITEM,
            from = TodoListActions::class)
    fun addItem(state: List<TodoItem>, item: TodoItem): List<TodoItem>  {
        return state.plus(item)
    }

The Action:

    @ActionCreator.Action(ADD_ITEM)
    fun addItem(item: TodoItem): Action

Note, that the following horrible version of the reducer does build in Kotlin:

    @AutoReducer.Action(
            value = TodoListActions.ADD_ITEM,
            from = TodoListActions::class)
    fun addItem(state: List<Any>, item: TodoItem): List<TodoItem>  {
        return (state as List<TodoItem>).plus(item)
    }

Since "List<? extends X>" parameter does not have an equivalent in Kotlin, as far as I know, do you know a better solution to get my reducer to build?

@jhavatar
Thank you for this issue. To be honest, I didn't spend much effort on Kotlin support :), but that was my next big step.

I need to investigate why Kotlin represents type differently from Java while compilation.

One ad-hoc solution that comes to mind: you can still declare your args as List<? extends X>:

    @AutoReducer.Action(
            value = TodoListActions.ADD_ITEM,
            from = TodoListActions::class)
    fun <T> addItem(state: T, item: TodoItem): List<TodoItem>  where T : List<TodoItem> {
        return state.plus(item)
    }

As that is a dirty and ugly solution, this should work.
I will look into it and add first class support for Kotlin types soon.

@jhavatar No, sorry, the last example doesn't work either with the production version of Reductor, sorry.
Another workaround is to wrap List into some other class which doesn't have generics.

cool, thanks Yarikx.
I'll do a hack for now. Please reply on this thread when a version is released with updated kotlin support.

@jhavatar Ok, I found a solution!
http://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#variant-generics

So as the issue is caused by kotlin-specific declaration-site variance and Java interop, it cannot be really solved from the reductor side.
Instead, you can tweak type declarations to help compiler a bit. The options are:

  • Get rid of wildcard in Reducer type parameter
@AutoReducer
abstract class KotlinReducer : Reducer<List<@JvmSuppressWildcards TodoItem>> {
  ...
}

That will make Kotlin represent the class as Reducer<List<TodoItem>> and not as Reducer<List<? extends TodoItem>>

  • Leave Reducer state as is, but annotate state argument in each action handler with @JvmWildcard
    fun addItem(state: List<@JvmWildcard TodoItem>, item: TodoItem): List<TodoItem>  {
        return state.plus(item)
    }

Hope that helps.

Yes, solves the problem. Thanks, will come in handy.