Benoît
10/25/2021, 9:10 PMfold()
and dispatch and effect on the left case (error), and a new state on the right case (success), within the same fold()
Using Orbit-MVI I either have to fold the apiResponse
twice in the reduce block and the suspend block from the intent
(option1), or do the fold()
outside the reduce block but then the fold is happening in the impure world which isn't good practice in my opinion.
Which option do you think is best, and is there any alternative to achieve this?kenkyee
10/25/2021, 9:25 PMBenoît
10/25/2021, 9:35 PMpostSideEffect
is a suspend function, so no it's not possible. Posting a side effect is an impure thing.
Mobius deals with that by having the equivalent of the reduce
block (the Update function) returning a Next<State,Effect>
which is just a State?, Set<Effect>
Benoît
10/25/2021, 9:46 PMreduce
block returns Unit, there's no way to know what happened inside it. Sometimes depending on what happened in reduce
I would want to do a return@intent
and just stop the computation herekenkyee
10/25/2021, 11:38 PMBenoît
10/26/2021, 12:08 AMMikolaj Leszczynski
10/26/2021, 8:28 AMreduce
to keep it simple.
• In Option 2 - can you inline the reduce into success
? What does having it outside give you? We don’t really need to return anything from the fold - looks to me like it’s kind of done for the sake of being “functional” (please take no offense, just commenting on the code as I see it 🙂 )
• after reduce
runs you can simply get state
to get the post-reduction state - reduce operations suspend until completed. Do you think it would be better for the reduce
op to return the result? Not opposed to the idea if it makes things more streamlined.
I’m not very familiar with Arrow so let me know if I’m suggesting weird things.Mikolaj Leszczynski
10/26/2021, 8:36 AMResult
which is functionally similar to Either
Mikolaj Leszczynski
10/26/2021, 8:48 AMBenoît
10/26/2021, 8:48 AMreduce
is blocking and executed on a single thread ensures that if 2 events come in at the same time, they will both be taken into account.
Acting on the state outside the reduce
block allows for some edge cases where the state would be modified by 2 different threads at the same time, essentially discarding one of the results.
So in order to avoid this type of edge cases, option 1 is actually better, but it forces us to consume the apiResponse twice.
And yes you are right, Either
is basically a Result
with more capabilitiesMikolaj Leszczynski
10/26/2021, 8:50 AMActing on the state outside theThe result of oneblock allows for some edge cases where the state would be modified by 2 different threads at the same time, essentially discarding one of the results.reduce
reduce
would only get discarded if the second one acts on the same part of the state, no?Mikolaj Leszczynski
10/26/2021, 8:51 AMMikolaj Leszczynski
10/26/2021, 8:52 AMreduce
returning so I’m happy to make that change if it would make life easier for youBenoît
10/26/2021, 8:52 AMList
in my state, if I want to modify 1 item of the list it would have to happen in a reduce block, as modifying a List
on an immutable object means copying it.
Think of the following scenario:
Some thread reads the state outside the reduce block, modifies item at index 1
Some thread reads the state outside the reduce block, modifies item at index 0
Then thread 1 triggers the reduce
function with its new state, everything is fine.
Then thread 2 triggers the reduce
function, erasing what thread 1 just didBenoît
10/26/2021, 8:53 AMreduce { myNewState }
ignoring the current stateMikolaj Leszczynski
10/26/2021, 8:54 AMreduce
blockBenoît
10/26/2021, 8:54 AMBenoît
10/26/2021, 8:55 AMMikolaj Leszczynski
10/26/2021, 8:55 AMreduce
Mikolaj Leszczynski
10/26/2021, 8:55 AMMikolaj Leszczynski
10/26/2021, 8:56 AMapiResponse.fold(
...,
{ success ->
reduce { state.copy(...) }
}
)
Benoît
10/26/2021, 8:56 AMsuccess->
reduce { state.copy(...) }
Mikolaj Leszczynski
10/26/2021, 8:57 AMreduce
return - if this is something that would make things easier for youMikolaj Leszczynski
10/26/2021, 8:57 AMBenoît
10/26/2021, 8:57 AMBenoît
10/26/2021, 9:03 AMBenoît
10/26/2021, 9:04 AMMikolaj Leszczynski
10/26/2021, 9:04 AMBenoît
10/26/2021, 9:05 AMBenoît
10/26/2021, 9:05 AMMikolaj Leszczynski
10/26/2021, 9:05 AMMikolaj Leszczynski
10/26/2021, 9:05 AMMikolaj Leszczynski
10/26/2021, 9:06 AMBenoît
10/26/2021, 9:07 AMdata class ReduceResult<out S : Any, out F : Any>(val state: S?, val effects: Set<F>)
Benoît
10/26/2021, 9:08 AMMikolaj Leszczynski
10/26/2021, 9:09 AMBenoît
10/26/2021, 11:49 AMcontainerContext
is internal
in SimpleSyntax
? It is preventing me from writing my own implementation of reduce{}
Mikolaj Leszczynski
10/26/2021, 11:50 AMBenoît
10/26/2021, 12:05 PMMikolaj Leszczynski
10/26/2021, 12:05 PMMikolaj Leszczynski
10/26/2021, 12:06 PMMikolaj Leszczynski
10/26/2021, 12:06 PMappmattus
10/26/2021, 12:07 PMMikolaj Leszczynski
10/26/2021, 12:09 PMappmattus
10/26/2021, 12:13 PMMikolaj Leszczynski
10/26/2021, 12:15 PMMikolaj Leszczynski
10/26/2021, 12:15 PMMikolaj Leszczynski
10/26/2021, 12:15 PMMikolaj Leszczynski
10/26/2021, 12:15 PMappmattus
10/26/2021, 12:19 PMBenoît
10/26/2021, 12:34 PMintent()
function, with a transformer
that would be expecting something else than SimpleSyntax
So since one could already abuse it, I don't think it matters muchappmattus
10/26/2021, 12:36 PMfun option3() = intent {
networkCall().fold(
{ error -> postSideEffect(ShowToast("Error")) },
{ success -> reduce { state.copy(stateString = "Success") } }
)
}
ultimately there's nothing pure about any of this code, reduce
is a side effect from a functional point of view as your affecting a different system, as is postSideEffect
and of course the networkCall()
appmattus
10/26/2021, 12:40 PMstate
inside the intent
block is impure and can change across the execution of that blockBenoît
10/26/2021, 12:40 PMreduce
function ouputs a new state + a set of effects, this way dispatching effects happens in the pure world.
Another issue with these 3 options is that they can only be written inside an intent()
which tightly couple them to the Orbit-MVI framework. With Mobius, the logic can be written outside any MobiusLoopBenoît
10/26/2021, 12:41 PMstate
can change in intent
that's why most of the logic should happen in the reduce blockappmattus
10/26/2021, 12:42 PMappmattus
10/26/2021, 12:43 PMBenoît
10/26/2021, 12:43 PMBenoît
10/26/2021, 12:44 PMThink of the following scenario:
Some thread reads the state outside the reduce block, modifies item at index 1
Some thread reads the state outside the reduce block, modifies item at index 0
Then thread 1 triggers the reduce function with its new state, everything is fine.
Then thread 2 triggers the reduce function, erasing what thread 1 just did
This shows that even reading the state is best in the reduce block, only side effects should happen outsideBenoît
10/26/2021, 12:44 PMappmattus
10/26/2021, 12:55 PMBenoît
10/26/2021, 12:57 PMhandleResult( currentState, networkResponse ) : Ouput<State,Set<Effect>>
This function could then be called inside a reduce block but it shouldn't need the reduce block to runkenkyee
10/26/2021, 2:45 PMBenoît
10/26/2021, 3:08 PMNext<State,Effect>
approach
The state and the effects are separated, but a reduction can trigger the output of an effect. But in the Next itself, states and effects are 2 different val
Benoît
10/26/2021, 3:11 PMContainerContext
in the SimpleSyntax
class (which btw could be an interface) or do you want me to fork the repo?
I don't mind doing so, I completely understand that Orbit-MVI wasn't necessarily thought of with functional programming in mind and I thank you for helping meappmattus
10/26/2021, 3:11 PMSimpleSyntax
Benoît
10/26/2021, 3:12 PMContainerContext
, subscribedCounter
is internal, any reason behind this? Anything that could break if it was exposed?appmattus
10/26/2021, 3:14 PMBenoît
10/26/2021, 3:15 PMrepeatOnSubscription
implementations, not that I need it, but from what you said you want Orbit-MVI to be extensible
I can put up a PR with these 2 changes if you wantappmattus
10/26/2021, 3:16 PMBenoît
10/26/2021, 3:16 PMappmattus
10/26/2021, 3:23 PMThink of the following scenario:
Some thread reads the state outside the reduce block, modifies item at index 1
Some thread reads the state outside the reduce block, modifies item at index 0
Then thread 1 triggers the reduce function with its new state, everything is fine.
Then thread 2 triggers the reduce function, erasing what thread 1 just did
this type of scenario is why state changing should only happen in reduce and be a copy of it's captured state... thread 1 should only modify index 1 in its reduce function and thread 2 should only modify index 0 in its reduce function. you only modify what should change and you don't rely on the state outside of reduce.
of course if both thread 1 and thread 2 may alter the same index or field then you cannot stop this type of issue.Benoît
10/26/2021, 3:32 PMreduce
, this is also the reason why I want to be able to do more in reduce
than simply updating the state. In my experience, the more code you can put in a pure context, the better.
One common scenario is, if you have a list of business objects and you want to update one of them, you'd check if it exists in the reduce
block, if it doesn't then you probably want to stop the full intent
. For cases like this, you want to know what happened during the reduction.appmattus
10/26/2021, 4:25 PMBenoît
10/26/2021, 4:55 PMBenoît
10/26/2021, 4:57 PMappmattus
10/26/2021, 4:58 PMMikolaj Leszczynski
10/26/2021, 6:52 PMBenoît
10/26/2021, 6:58 PMMikolaj Leszczynski
10/27/2021, 7:11 AMMikolaj Leszczynski
11/01/2021, 10:12 AMBenoît
11/01/2021, 10:39 AM