-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Most general form of feedback loops #7
base: master
Are you sure you want to change the base?
Conversation
- Implemented initial version of AsyncSynchronized to synchronize state mutation.
- To do `areEqual` parameter is not needed anymore in Swift but we might still want in Kotlin. This should be discussed further with Zaher and Juraj. - Changed the other `react` methods to use the feedback loops as a general
Nice! Thanks for contributing.
I went through the PR and I’m having hard time understanding what’s going
on. Namings like `Lifetime...` are not intuitive to me. Could you please
add some comments explaining what’s happening? For example, class comments
for the `RequestLifetimeTracking` and its nested classes.
Also, would love to get rid of the optional guy. In my apps I use the
Arrow’s optional. This generalization will help delete our version. Even if
we don’t manage to delete it, I’d like to make it internal and expose an
interface with standard kotlin types.
Thanks,
Marko
On Sun, 5 May 2019 at 16:44, eliekarouz ***@***.***> wrote:
This pull requests includes the most general form of feedback loops that
was implemented on RxFeedback.swift. TODO:
-
Unit Tests
This is still a draft as I still need to add the unit tests for the
new feedback loops. I am not very familiar with unit testing with
TestScheduler. I will be doing some research to understand how this
works.
-
Equatable
In the Swift version of this library, we are forcing the Query to be
equatable. There was an overload that takes areEqual: (Query, Query)
-> Bool. This was removed from RxFeedback.swift . @kzaher
<https://github.com/kzaher> I think this was needed for arrays because
prior Swift 4.2 to handle cases like these: Array<Equatable> was not
Equatable. but I think we still need in Kotlin because we need to call
special methods for deep comparison of arrays.
We might need to add an overload to the most general form that takes a (Query,
Query) -> Boolean parameter.
-
Optional<T>
Regarding the Optional type. I am not sure but I don't think this is
needed. Maybe we can provide a new API without Optional and deprecate the
current ones that use Optional. We can in a future version decide to remove
them.
We can keep though the Optional type as it can be used to wrap Kotlin
nullables when we need to have to build streams with nullable values.
------------------------------
You can view, comment on, or merge this pull request online at:
#7
Commit Summary
- Most general form of FLs:
- - Implemented the most general form of feedback loops.
- Better handling of `areEqual`.
- Added `reactSafe` for `Equatable`
File Changes
- *A*
rxfeedback/src/main/java/org/notests/rxfeedback/AsyncSynchronized.kt
<https://github.com/NoTests/RxFeedback.kt/pull/7/files#diff-0> (36)
- *M* rxfeedback/src/main/java/org/notests/rxfeedback/Feedbacks.kt
<https://github.com/NoTests/RxFeedback.kt/pull/7/files#diff-1> (272)
Patch Links:
- https://github.com/NoTests/RxFeedback.kt/pull/7.patch
- https://github.com/NoTests/RxFeedback.kt/pull/7.diff
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#7>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABTW25PHCHFLVT2H7BNTYO3PT3XDVANCNFSM4HK3NCQA>
.
--
Pozdrav,
Marko
|
@eliekarouz yes, that was because of compiler limitations :(. |
@markotron I've refactored the feedback loops in Swift version so that they are all just simplifications of one general form. Seemed elegant to me. The idea is to have a set of identifiable values controlling the lifetime of observable state machines. When a new value is added to a set, new observable state machine in effects is subscribed to. When it is removed the subscription is disposed/unsubscribed -> effectively killing the state machine inside the observable. If the value for some key changes that is sent to the We've renamed |
I see. It does seem more elegant.
I’m still unfamiliar with some terminology that you use like: *observable
state machine, identifiable values *(is that just an object that can be
hashed?).
I get the general idea, I’ll take a look tmrw morning again and ask more
things probably :P
On Wed, 8 May 2019 at 20:37, Krunoslav Zaher ***@***.***> wrote:
@markotron <https://github.com/markotron> I've refactored the feedback
loops in Swift version so that they are all just simplifications of one
general form. Seemed elegant to me.
The idea is to have a set of identifiable values controlling the lifetime
of observable state machines. When a new value is added to a set, new
observable state machine in effects is subscribed to. When it is removed
the subscription is disposed/unsubscribed -> effectively killing the state
machine inside the observable.
If the value for some key changes that is sent to the state parameter
inside (intialQuery: Query, state: Observable<Query>) -> Observable<Event>
.
We've renamed Query to Request. People were complaining about the name
Query. I think it might make sense to rename to Kotlin version also.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#7 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABTW25PFSCY7PO7JWXC7HVLPUMMVNANCNFSM4HK3NCQA>
.
--
Pozdrav,
Marko
|
observable state machine -> Observable |
Sure. Will work on it this weekend... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the interesting PR! It was fun reviewing it :)
Also, does the AsyncSynchronized
concept have a name? Is it used somewere else as well? I'd like to read more about it.
queryLifetime.latestQuery.onNext(query) | ||
} else continue | ||
} else { | ||
val subscription = CompositeDisposable() // SingleAssignmentDisposable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using a CompositeDisposable
you could have rearranged this so that you add a new QueryID
to the lifetimeByIdentifier
map after subscribing to the effect, right? In this case, you don't need a ComponsiteDisposable
that will actually contain just one Disposable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah you are right... I was not very convinced of what I was writing there...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, does the
AsyncSynchronized
concept have a name? Is it used somewere else as well? I'd like to read more about it.
Thanks for the feedback... Maybe SerialQueue
? I don't think there is a standard definition for for this concept because the mutations can run on any thread vs serial execution queues which is a know concept on iOS. With serial execution queues all mutations are dispatched to the same thread. Not sure if @kzaher has any link to share with us...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@markotron , I think it's called AyncLock
* as well as update already activated effects with the new Query that was returned. | ||
*/ | ||
private class QueryLifetimeTracking<Query, QueryID, Event>( | ||
val effects: (intialQuery: Query, state: Observable<Query>) -> Observable<Event>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now the effects
accepts the state: Observable<Query>
as a parameter, but in all our current helpers we're just ignoring this parameter:
effects = { initial: Query, _ ->
effects(initial)
}
What is the use case of this state
parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea of the general form is to have some standard way to implement nested systems while still updating the inner systems with the parent substate/state changes.
The react
and reactSet
can be seen as a projection (subset) of the general form. It would be better to provide the general form but also refactor existing react
methods to use the general form in order to reduce the code base...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I understand why we have a general form. What I meant is that the general form is more general then all our current use cases (actually, my use cases :)). I thought that the implementation in swift was created with a specific use case in mind and wanted to know what that use case is. @kzaher did you use this general form for something that cannot be described without a general form?
|
||
private val queue = mutableListOf<Mutate<State>>() | ||
|
||
fun async(mutate: (State) -> Unit) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be named async
? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@markotron ,
mmm... Do you have any other suggestion? It's called a async
because it the mutation to the state can be performed after the async
completes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that’s what confused me. It can be performed after the async
completes. But in some cases it want. Actually it will last even longer (unbounded) if other threads keep queuing tasks. When you say that something is async, I would assume that it will just queue something and complete.
So I would name this something like queueOrExecuteAll
(or something similar).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it will last even longer (unbounded) if other threads keep queuing tasks. When you say that something is async, I would assume that it will just queue something and complete.
Good point! I will rename it to queueOrExecuteAll
. It communicates the intent better.
Btw. I suggest we drop the optional and introduce a breaking change. So that new versions actually have a form without the optional. In my app I use the Also, I don’t follow the second bullet ( One last question. Is |
I agree and in case anyone doesn't have time to do the migration he can still create a helper method.
It was removed in RxFeedback.swift.
I can do this in another pull request. In RxFeedback.swift, they renamed |
So How do you define a state machine? You have a |
@markotron |
Nice! I was hesitant a bit about that name :) Good! |
@eliekarouz — what are the reminding things here? Tests? |
} | ||
} | ||
|
||
// queries | ||
val State.loadNextPage: Optional<String> | ||
val State.loadNextPage: String? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, on line 154 we pass null inside a chain because of this change:
// line 154
it.map { it.loadNextPage }.drive { showQuery(it) }
We had optional here because in rxJava you'll get NPE immediately if you pass null through the chain.
https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#nulls
This works here accidentaly because we're using Driver
that has try-catch
block inside it's map
function so it swallows NPE but nulls are not supposed to be used in rxjava2.
That was also the reason why we used Optional
for nextPageUrl
and lastError
(ok we currently don't map a state to these fields so that could remain nullable)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eliekarouz — @JurajBegovac is correct. Although you refactored feedback loops in a way that they don't require Optional
s, this guy still needs it.
Instead of removing an optional here, you should convert it on line 162.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JurajBegovac , @markotron
Yeah you are right. I missed it in the map operator. Actually I didn't test the example yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JurajBegovac , @markotron , We can transform them to empty strings so we don't have to add the back the Optional
. I know it's a bit dirty... A cleaner solution would be to add back the Optional
but make it internal to the library or even public if you want.
if (error is Optional.Some) { | ||
Toast.makeText(this, error.data.displayMessage, Toast.LENGTH_SHORT).show() | ||
private fun GithubPaginatedSearchActivity.showOrHideError(error: GitHubServiceError?) { | ||
error?.let { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I said in comment before - when we reach this place error would never be null because chain will throw NPE before (it cannot pass null) and Driver will swallow it (it looks like it works but it only works because of Driver)
Mainly tests but not sure if I will be able to do anything this weekend. |
This pull request includes the most general form of feedback loops that was implemented in RxFeedback.swift. TODO:
Unit Tests
This is still a draft as I still need to add the unit tests for the new feedback loops. I am not very familiar with unit testing with
TestScheduler
. I will be doing some research to understand how it works.Equatable
In the Swift version of this library, we are forcing the
Query
to be equatable. There was an overload that takesareEqual: (Query, Query) -> Bool
. This was removed from RxFeedback.swift . @kzaher I think this was needed for things like arrays because prior to Swift 4.2Array<Equatable>
was notEquatable
. but I think we still need this overload in Kotlin because we need to call special methods for deep comparison of arrays.If you agree, i will add an overload to the most general form that takes a
(Query, Query) -> Boolean
parameter.Do you think we need this in Swift?
Optional<T>
Regarding the
Optional
type. I am not sure but I don't think this is needed. Maybe we can provide a new API without Optional and deprecate the current ones that use Optional. We can in a future version decide to remove them.However, we can keep the Optional type in the library as it can be used to wrap Kotlin nullables when we need to build streams with nullable values.