Short description of the issue:
I found out that current Single trait's flatMap returns Single.
I've got curious though, because even when source observable emits once, emitted observable can emit any number of elements right?
If I'm misunderstanding the concept, it's very welcome to be corrected. Thank you.
Expected outcome:
Single's flatMap returns Single
What actually happens:
I think it should return just observable, since nobody knows how many elements will be emitted from the observable.
_what actually happens goes here_
Self contained code example that reproduces the issue:
let mySingle = Single<Int>.just(1)
mySingle.flatMap { value -> Observable<Int>
//Currently, the output only accepts single like Single<Int>
//But I thought at this point, we should be freed to emit multiple elements
}
RxSwift/RxCocoa/RxBlocking/RxTest version/commit
_version or commit here_
Platform/Environment
How easy is to reproduce? (chances of successful reproduce after running the self contained code)
Xcode version:
Hi @0xF0D0 ,
Can you please write a code example that you either:
I can't understand your question.
My question is "why single's flatMap only returns single?".
It's the code of single's flatMap:
public func flatMap<R>(_ selector: @escaping (ElementType) throws -> Single<R>)
-> Single<R> {
return Single<R>(raw: primitiveSequence.source.flatMap(selector))
}
As you see return type is Single which means the returned observable only emits one element.
But what if the case like figure below happens?

Situations like output of each elements emits two elements cannot be handled by current single's flatMap
So basically what i'm proposing is why we woudn't add something like this:
public func flatMap<R>(_ selector: @escaping (ElementType) throws -> Observable<R>)
-> Observable<R>
This operator is also available on RxJava
Hi @0xF0D0 ,
the reason why we don't have it is because you can just do asObservable().flatMap and then you have both flatMap and any other Observable operator.
We've only added the overloads provide additional compile time information, like maybe, single and completable.
You can always drop from anything to Observable by just doing asObservable.
Hi @kzaher
From my understanding, there are two completely distinct behaviors between
func methodThatReturnsAnObservableThatEmitsMultipleItems(text: String) -> Observable<Int> {
return doSomething(text: text)
}
mySingle.flatMapObservable { textString in
return methodThatReturnsAnObservableThatEmitsMultipleItems(textString)
}
//Now, mySingle is an observable that emmits multiple items, and depending on how doSomething is implemented, this observable may never complete
and
func methodThatReturnsASingle(text: String) -> Single<Int> {
return doSomething(text: text)
}
mySingle.flatMapObservable { textString in
return methodThatReturnsASingle(textString).asObservable()
}
// Now, my single is an observable, but only emits one item and completes.
The operator asObservable for me, is just to make the compiler happy, and flatMapObservable is an operator that allows composition between a stream that emits only one item to a stream that emits many items and may or may not complete.
Does that make sense?
Hi @pietrocaselani ,
I think I understand the code, but I'm not sure I can clearly see an argument or benefit of having this form
public func flatMap<R>(_ selector: @escaping (ElementType) throws -> Observable<R>)
-> Observable<R>
on Single.
As I mentioned on #1580 , I think that those operators are important to allow composition between different streams, and I like the idea of keeping consistent with RxJava and others Rx implementations.
To me, doing flatMap{...}.asObservable() looks boilerplate.
IMO, in the first place, flatMap -> Single<R> shouldn't be the default flatMap operation, since it restricts other conversion as @pietrocaselani said.
How about splitting flatMap into flatMapSingle & flatMapObservable?
Hi, guys, I think @kzaher point that the main library does not need these (unnecessary) overloads, so that we can keep it smaller, more maintainable and provide core functionality.
And if you really want to have these overloads you can easily achieve this just by adding extension method to the Single ¯_(ツ)_/¯
@sergdort I do agree with you and @kzaher's idea to maintain the library small.
I was just wondering why flatMap -> Single<R> had to be the default though... since it provides less functionality
@0xF0D0 Because you are using Single to begin with :)
If you have a Single(1) and you do map { $0 * $0 }, you would still expect to get a Single so you retain uniformity. You'd be confused getting an Observable when mapping a Single, and that case would definitely be the same for flatMaping a Single and getting back an Observable all of a sudden - it doesn't make much sense.
Adding asObservable() is a tad explicit - but that's not necessarily a bad thing. Single makes a specific promise, and the fact you're explicitly "converting" it to an Observable potentially helps other readers of your code understand assumptions you're making about that sort of composition.
As mentioned above, if you have a strong urge to move away from asObservable(), creating your own extension would be a fairly minor task to have. We can also consider adding to RxSwiftExt.
@freak4pc Thanks for the kind reply :)
It really scratched my back 👍
I'll close this issue since my all curiosity have solved.
Most helpful comment
Hi, guys, I think @kzaher point that the main library does not need these (unnecessary) overloads, so that we can keep it smaller, more maintainable and provide core functionality.
And if you really want to have these overloads you can easily achieve this just by adding extension method to the
Single¯_(ツ)_/¯