ReactiveX / RxSwift

Reactive Programming in Swift
MIT License
24.38k stars 4.17k forks source link

Method compose() from RxJava #612

Closed robertofrontado closed 6 years ago

robertofrontado commented 8 years ago

Hello guys,

I want to know if there is an equivalent of compose() from RxJava in RxSwift?, I couldn't find anything

Reference: http://reactivex.io/RxJava/javadoc/rx/Observable.html#compose(rx.Observable.Transformer)

kzaher commented 8 years ago

Hi @robertofrontado ,

I've read http://blog.danlew.net/2015/03/02/dont-break-the-chain/

I don't think we have the same problem in Swift, you can just define an extension on ObservableType

extension ObservableType {
    public func applyOperators() -> Observable<E> {
         return self.subscribeOn(backgroundScheduler)
                .observeOn(MainScheduler.instance)
    }
}
robertofrontado commented 8 years ago

Thanks @kzaher,

Yeah, I was reading that blog too, and I just wanted to know if there was already that function, so I'm going to do that

vander2675 commented 6 years ago

@kzaher I love the clarity Extension functions bring into the chain. But I have a problem when trying to mock those for Unit testing purposes, wich would be possible with ObservableTransformer and the compose() Method. As of I know, it‘s not possible right now To mock an Extension function. Pls correct me if I‘m wrong!

kzaher commented 6 years ago

@vander2675 I think these two approaches are equivalent in power

extension ObservableType {
    public func applyOperators(scheduler: SchedulerType) -> Observable<E> {
         return self.subscribeOn(scheduler)
                .observeOn(scheduler)
    }
}

I think RxJS is going this way with pipe operator also. It would be great if RxJs and RxJava would unify compose and pipe into some name.

As far as I'm concerned, this should be trivial to implement, and wouldn't our code base too much, so maybe we should revise this.

vander2675 commented 6 years ago

@kzaher As far as i understand is the pipe Operator also implemented to not pull the whole operators dependencies into the project. And the Syntax is different to the compose from RxJava. So you share my opinion about missing testability from Extensions?

I allready tried out an approach, wich I can share if you like?

kzaher commented 6 years ago

As far as I can tell none of these operators is introduced because of testability and dependency injection, that is just a consequence of the fact they allow you to define "operators" in certain scope.

vander2675 commented 6 years ago

Sorry I don‘t really understand what you mean. Do you mean the Operators like map, filter are not introduced because of Testability or DI? I don‘t want to introduce compose() with ObservableTransformers because of testability. Just to make it possible, wich is not with Extension functions. All other Operators are testable. Or did I missunderstand you?

kzaher commented 6 years ago

I mean that neither of the operators, including pipe and compose are introduced because of testability.

Just to make it possible, wich is not with Extension functions. All other Operators are testable.

Again, you can make testable operators yourself. You need to expose dependencies as arguments.

extension ObservableType {
    public func applyOperators(scheduler: SchedulerType) -> Observable<E> {
         return self.subscribeOn(scheduler)
                .observeOn(scheduler)
    }
}

... or one can define their own compose operator and use anonymous lambdas.

Testability of the operators ~ ability to inject dependencies

compose and pipe ~ ability to scope operators (anonymous transformer lambdas) per specific regions (files, classes, structs, enums, functions ....)

kdawgwilk commented 6 years ago

For those curious here is my implementation:

protocol Transformer {
    associatedtype Upstream
    associatedtype Downstream

    func apply(_ events: Observable<Upstream>) -> Observable<Downstream>
}

extension ObservableType {
    func compose<T: Transformer>(_ transformer: T) -> Observable<T.Downstream> where E == T.Upstream {
        return transformer.apply(self.asObservable())
    }
}

Now you can create your own Transformer types and compose them easily. This also allows you to keep types abstract so you can mock your own transformers for unit testing

malaba commented 6 years ago

Another way is a functional approach, instead of a OO approach:

extension ObservableType {
    func compose<R>(_ transformer: (Observable<E>) -> Observable<R>) -> Observable<R> {
        return transformer(self.asObservable())
    }
}

No need for the protocol Transformer.

You use it like that:

func schedulingStrategy<T>(_ observable: Observable<T>) -> Observable<T> {
    return observable
        .subscribeOn(scheduler)
        .observeOn(scheduler)
}

someObservable
   .compose(schedulingStrategy)
   .subscribe()

You will need some sort of Factory to generate those func so that they capture external var like scheduler.

kzaher commented 6 years ago

@malaba, yes, this is the form that has a more Swift like interface.

Rxjs also has pipe that is quite simular, although pipe is more powerful.

freak4pc commented 6 years ago

I was actually talking about this operator the other day. I don't think we necessarily need this in RxSwift core, but apply from RxSwiftExt does exactly what you're looking for, to the best of my understanding: https://github.com/RxSwiftCommunity/RxSwiftExt#apply

Closing this issue for now as its been too long standing and there have already been several implementation ideas here for people who'd like to add to their codebase.

Please let me know if you have additional questions regarding this or if you feel this needs to be re-opened.

Thanks!