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
Stream of one event. #661
Comments
Some operators on |
This is a great idea, although I would like to see the same arguments used when I proposed this (#201) and got shot down. :) ( cc @NachoSoto and @liscio ). |
I also think this is a great idea. Our codebase is littered with a convenience function which does: map { transform($0).producer }.once() I would say that (in our codebase) maybe 50% or more of UPDATE: I also think that having |
I am already desperate.. monitoring the topic in general for more than 6 months already. I very much need pure promises in my code base, cause dealing with Signals and SignalProducers is just not OK. |
Looks awkward, but maybe we could introduce here Edit: sorry about the replayLazily() above, it's not relevant here. I was thinking about "retry" semantics when added that note |
tl;dr: Making another lib that leverages RAS underneath and implements the Promises A+ spec could be something I could do. Hi @leonid-s-usov thank you for your input, we appreciate feedback regarding how users take advantage of ReactiveSwift and how we can enhance their experience while doing so. Regarding your specific use case: request.then { map($0) }.then { consume($0) }.catch { ... }`
/// versus
.flattenMap(.Latest /*.Merge? .Concat?*/) { map($0) }.startWithResult { ... } It's important to notice a couple of things:
As for the general idea of Promises, I don't think the inclusion of |
tl;dr: I would be happy to help with the Promises A+ layer over RAS. To be honest, it still takes me a minute every time I get to choose between the latest, merge and concat, usually after some time of working on other, not reactive parts of my code. But I do know the difference and can always deduce the correct value eventually. I am actually using My problem is that whenever I am trying to reinvent this And, reviewing the code of my colleagues I can see all three strategies (well, mostly two, the .latest and .merge) being used, and all of the variants actually working. The actual I am actually having this task on my todo - implement a wrapper for ReactiveSwift having the Promise semantics. But you know, before getting there (which would be a fun task for sure) I was hoping that someone already did that or at least was planning to. Lastly, I am not sure what you meant by
I think it actually would be one step closer as that would give me (and anyone else) a special flatten strategy for the "promise chaining" use case, maybe even with some assertions to guard the "single event" expectation. It would be just disappointing to me because this route doesn't look like anything which would lead to the promises API So, I'm totally for any initiative into the direction of having a well tested and concise Promise-like API on top of RAS. I've seen PromiseKit actually created a testing environment where they run the original JS A+ test suite on their implementation, isn't that cool?! |
I think the way forward then would be open an issue here. Curious enough someone had a simillar idea RACCommunity/contributors#8 |
OMG thanks much! |
I still see how any implementation would benefit from |
My personal stance is that we should have a cold entity, that isn't named I am sure that the warm behaviour of As for naming, there is a dissent around "inheriting" the name After days of thoughts the best I can come up with is |
What about a Still I can't easily get away from the kind of warm object which would actually hold the signal, and then eventually the result of the operation until the end of its lifetime. I mean, if we only have the cold one shot, then one would need to use some other layers to pass this eventual result on to consumers, if we want that the work is done on the background while we are sending the "yet to be prepared" result promise to all the interested parties. To me that's one of the important reasons to search for the kind of interface. Let's be specific and take a simple example of a network request. I have some function inside some
What would my code look like?
Well, this is not anything better than what I can achieve with SignalProducer today.
For that only... I'd be better off with the If we are for some actual development around this, I'd want to get rid of these ugly "configure" methods. I want to call
Did you notice? I have used the I would agree that a cold entity can bring many useful things but we'd still have to have some promise-like thing that this cold entity resolves to. Just like |
Maybe, it's not the cold object that we are lacking, but rather this Promise-like container which accepts a SignalProducer or a Signal in constructor, keeps the internal state of whether it has ever received the value or not, and supports the "then" semantics? Because, all the higher level tricks around replays and stuff can simply be done on the passed-in SignalProducers. As its second task, the object may give the users some syntactic sugar to conveniently create a promise with a setup function which doesn't receive an observer, but rather some Quite what the guys at OMsignal did in their ReactiveFuturesKit. Just don't pay too much attention to the class names there, the Promise they have used is very misleading. Their actual 'OneShot' class is the
|
I totally agree 👍
Why is there dissent around using
I think that having talked about Alternatively, the let loadUser: Single<User, NetworkError>
// ...
loadUser
.then { user -> Single<[Post], NetworkError> in
posts(for: user)
}
.then { posts -> Single<[Comment]>, NetworkError> in
comments(for: posts.first)
} |
IMO |
How about |
@mluisbrown
|
Good point. You would still have to
Using |
I still don't really see the point of this, sorry. A I am currently wrapping up some work in an API that uses I also use
Now, would I like some added comfort provided by the compiler and/or type system to guarantee that my Is it worth giving up the simplicity of having "two base types"[^1] that are vended by If folks are having trouble with explaining Frankly, if you really want [1] Sorry, |
@liscio, I think you did a great job at bringing the discussion back to the topic. Given the title of this issue, I would agree with you that there doesn't seem to be much benefit from having another cold entity acting mostly like SignalProducer. I personally came here in search of a solution to places where Promise semantics would make much more sense than returning SignalProducers
So, I was here to bring Promise into RAS, but you might be right, and this could be a wrong approach. And if not that, then what else would the "stream of one event" be which is not already a SignalProducer? I will however invest some time into exploring whether there's some nice way of "terminating" a Signal(Producer) with a Promise-like Thenable entity, in a concise and unambiguous fashion. |
I think it all depends on the specifics. You raise some good concerns. But I think it's conceivable that this concept could be added in a way that actually makes ReactiveSwift clearer. It definitely needs to be considered holistically. I think the I agree that if this concept probably shouldn't be added if it can't be added in a way that clarifies the whole. |
ReactiveSwift at a conceptual level is centred around I cannot dispute that it might in the end be a cognitive overload that offers little value over While @mdiep raised a good point in the need of reviewing the very basis before we proceed to extend the framework, “stream of one” is IMO orthogonal to that discussion, since this particular refinement is valid regardless of the base, which should remain by default multi-value with explicit termination. |
I can definitely envision a future (heh) where someone comes up with an adequate way to abstract a "one-shot, asynchronous operation" atop RAS' constructs, but I don't think that wrapping Using a different approach, what about starting with something that aims to replace both the For instance, consider a type called final class Async<Value, Error> {
init(_ action: (Promise<Value, Error>, Lifetime) -> Void) {
// Construct a normal SignalProducer, and use a new type, `Promise` to wrap the `send` API.
// Then, call action() inside the SignalProducer's constructor with the wrapper
}
// This type serves only to wrap the `send` API on Signal.Observer.
// Expectation could also be used instead of Promise to avoid confusion?
struct Promise<Value, Error> {
func succeed(with value: Value) {
// call send(value:) and sendCompleted() on underlying producer
}
func fail(error: Error) {
// call send(value:) and sendCompleted() on underlying producer
}
}
func await() -> Result<Value, Error> {
// basically just invokes startWithResult on the underlying producer
}
} Usage would look something like this: func doSomethingNetworky() -> Async<String, SomeNetworkError> {
return Async { promise, lifetime in
callSomeAsyncNetworkApi() {
if theCallSucceeded { networkStringValue in
promise.succeed(with: networkStringValue)
} else {
promise.fail(error: SomeNetworkError.someError)
}
}
}
}
let networkResult = doSomethingNetworky().await() And of course we could add the requisite Because they still rely on an underlying Thoughts? |
@liscio working on something pretty close to that, but I disagree with two things:
But in general I feel more or less on the same page. Let me just clear up these nasty type errors and I'll show a preview of what I mean. |
OK people, please check out this draft #664 Some highlights:
|
@liscio It seems you might have been mistaken here. The scope of “stream of one” has never been dead set on a warm This issue nominated a cold stream of one construct that is akin to Rx Speaking of my own stance, I have no intention to expand the API surface to support another style of async API. The acceptance criteria for me would be a familiar but refined API similar to |
@andersio BTW, what do you think about |
@leonid-s-usov Sorry, I was operating under a more "complete" My preference is not to stomp on the Anyway, @andersio it's clear that I'm not quite following the purpose, which is why I'm grasping at straws to understand the "big picture" value of this type. When you name your prototype The true test would be to see how this type—however it is named—improves the readability/usability of Giving us a handful of demonstrations of where it improves the call site / clarity will go a long way to help us understand what the ultimate goal is. |
I do like the name |
And here again, I think that But, following the |
Well ok, another implementation which decided on such term usage (which I find less complete). Splitting roles between "Future" and "Promise" this way is not something I personally can get along with, but that's subjective, of course. |
@leonid-s-usov Yep, totally subjective, and I should probably not have used the word "complete". The reason that it makes more sense to me is that—at the call site—you see that you are getting a value of a particular type in the From there, it follows that—in order to return/create a At any rate, this is obviously something that's interpreted in many ways, as the wikipedia page for this topic is quite dense. Now, I have no idea why you think that One thing you left out in your "calling by its name" is the fact that Anyway, this is way off-course from the original discussion now because this issue is not really trying to build a |
💯 |
👏 Can we please forget about As @liscio said, this issue is about creating an equivalent to Rx's
💯 Absolutely agree. I don't see any reason why RAS should not also adopt the |
@liscio
Well, the question about the cold / hot nature of the entity has been risen above, and not originally by myself. If you followed this from the beginning I was all for the warm entity - that is, the one where the operation starts behind the scenes and we can just consume the result. However, following comments from @andersio I agree that it would be great to have control - when needed - over when the background operation actually starts, and that would be pretty much in line with the current interplay between SignalProducer and Signal. This clarity is actually stated as one of the major concepts behind RAS, so I don't quite understand why you say its so unimportant. Hope that it's not just because of the naming. And |
@leonid-s-usov You're helping to strengthen my original comment of, "I still don't really see the point of this." 😄 Here's what I'm gathering (roughly) from the bulk of this thread… We want a producer that returns only a single value, or an error.Well, we have We want to get the value, similar to how
|
I think you are missing some points, let me continue your list We want code clarity when the returned producer will only fire once or fail There is no way to achieve this today. Interfaces are either semantically incorrect
or ugly
We want a clearer (and more efficient) The "correct" answer to this today is to use We (well, at least myself, a user of RAS) want a promise like functionality to avoid adding another library; to have interoperability between the provided promise and SignalProducer/Signal to utilise convenience functions and natively convert between interfaces within one framework Well, a lot has been said about this one. Even a Promise draft. We want other single value oriented operators, which would be more efficient, readable and safe for this common use case of real life reactive programming (@andersio please help me here) |
@leonid-s-usov We're effectively on the same page. When I said, "We want a nice type," I meant it in a way that's equivalent to "We want code clarity[…]" Regarding your "semantically incorrect" example, I am not sure you provided enough context to explain where the problem is. For instance, I see all three of the below as having a similar meaning: func getMessages(userId: String) -> SignalProducer<[Message], ApiError>
func getMessages(userId: String) -> Task<[Message], ApiError>
func getMessages(userId: String) -> () -> [Message] I read any of the above as, "give me a way to execute Maybe I'm missing something about how the returned producer is "hooked up" elsewhere to pass values along? Regarding a new |
Well, my point is that all of the methods above should have been called
which would make my OCD happy and I would know that whatever value returned is a "generator" which must be activated before any values are to be expected In contrast, what I would love to have is
where the returned value actually represents the result - but in this case, an asynchronous one. Or maybe, by the time we got to check it, the result could already be there. Or, it was returned from cache and can be used straight. |
It used to be a separate issue, but was closed in favour of this one |
As @mdiep mentioned the need of reviewing our base (Signal/SignalProducer), I tried to gather my thoughts around it. It turns out that my arguments followed this very same path of logic (link to a manifesto). It does pose an interesting doubt on whether a separate
(These are akin to the questions I had asked in #201.) |
@andersio So it looks like few people already agree that from the api consumer perspective a simple cold However IMO it will make much more sense if it will come with its warm promise-like counterpart. For the sake of this message let me temporarily pin the names: what you previously referred to as "cold Single" i'll call AsyncValueProducer and its warm counterpart (with benefits like caching the value and With these in place we can focus on using the |
I wonder what this silence means. Maybe this topic should be re-launched from scratch in a new issue, with some summary of this discussion like the one we had few messages above? WDYT? |
Please check out the new issue #667. I really hope it will facilitate the resolution of this thread - whatever the final decision will be. |
There's another POC implementation I've posted in #668 |
Anyone? |
In #668, it seems the example would look almost exactly the same with a The ability to have |
@jjoelson thanks for the feedback. If you don't mind, let's continue this discussion in #667 . That is a new issue I have created as an essence of this thread, and it has a convenient list of all properties of the potential new API. I will copy this comment there. I would agree with Having that said, personally I'm not married to any naming. My only strict criteria is that I believe if we choose to change something we need to be consistent and not create a mixture of patterns. |
@andersio there is a |
Hello. 👋 Thanks for opening this issue. Due to inactivity, we will soft close the issue. If you feel that it should remain open, please let us know. 😄 |
aka
Single
#201 or (lazy?)Promise
or (lazy?)Future
.Prototype
The RAC team is considering moving this forward. Apparently we would be able to provide a more concise API on top of the stronger compiler guarantees, and it is observed that the value-or-failure are quite commonly used, especially in modelling network requests or API queries.
A few design decisions need to be discussed:
Should it be cold or "warm"?
Proposal: Cold by default, opt-in replay via
replayLazily()
?Should it be called
Promise
orFuture
if it isn't "warm" by default?How should we model the event?
Proposal:
Should we support operator lifting as part of the public API? It can be made safe if we always apply
take(last: 1)
as part of the lifting.The text was updated successfully, but these errors were encountered: