etiennelenhart / Eiffel

Redux-inspired Android architecture library leveraging Architecture Components and Kotlin Coroutines
MIT License
211 stars 14 forks source link

Change `Update` to providing current state as receiver #73

Closed jordond closed 5 years ago

jordond commented 5 years ago

So I've been using Eiffel in my side-project for a bit now. And I've made a handful of Update's, and there's just one small thing that I feel like may improve QoL a little.

So currently the helper update takes a (State, Action) -> State lambda. I want to hear your thoughts on changing (or creating a new extension) that takes a lambda scoped to the State.

Something like:

fun <S : State, A : Action> update(block: S.(action: A) -> Unit): Update<S, A> {
    return object : Update<S, A> {
        override fun invoke(state: S, action: A) = state.apply { block(state, action) }
    }
}

It would allow for more concise Update code.

Like:

...
update = update { action ->
    when(action) {
       is Action.Loading -> copy(loading = true)
       is Action.Success -> copy(loading = false, list = action.payload)
    }
}

Again it's a small change, but it could be nice. One reason I like it is because my linter has a strict 100 character line limit, and saving 6 characters on each line is wonderful.

etiennelenhart commented 5 years ago

I'm always a bit cautious with receivers, especially in public APIs but since it's a pure function, possible clashes with other this are very unlikely. One goal I have for 5.0.0 though is to provide the same functionality for all users as much as possible, regardless of whether they're using an object oriented or more functional approach. So if they happen to create a class that implements Update, it should also provide them with the State receiver.

I'm doing something similar for the bindable mapping I'm currently working on. So I would change Update to something like this:

abstract class Update<S : State, A : Action> {

    abstract fun S.perform(action: A): S

    operator fun invoke(state: S, action: A) = state.perform(action)
}

fun <S : State, A : Action> update(perform: S.(action: A) -> S): Update<S, A> {
    return object : Update<S, A>() {
        override fun S.perform(action: A) = perform(action)
    }
}

Note that you don't have to use apply if you simply return S from perform and don't need to pass the receiver as the first parameter. You can use normal extension syntax like you would from outside the class or function.

So feel free to modify it and create a PR if you want.

jordond commented 5 years ago

I look into it this weekend! 💯

jordond commented 5 years ago

Had a busy weekend/week, but I got the PR up.