Skip to content
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

Transforming a sequence into a signal of sequence #262

Open
npvisual opened this issue May 5, 2020 · 3 comments
Open

Transforming a sequence into a signal of sequence #262

npvisual opened this issue May 5, 2020 · 3 comments

Comments

@npvisual
Copy link
Contributor

npvisual commented May 5, 2020

Context

I am finding, more often than not, that I am in need to transform a sequence (dictionary or array) into a signal of sequence.

This could happen either from an existing signal being flatMap'd to a sequence or simply an existing sequence from the imperative world that I need to transform (say via an API call on each element of the sequence) into a signal of sequence.

Taking the example of the chat room in AbsurdGitter. Let's imagine for a second that the Room entity holds a reference to the participants in the room, so something like :

public struct Room: Codable {
    public let id: String
    public let name: String            // Room name.
    public let uri: String?            // Room URI on Gitter.
    public let topic: String           // Room topic. (default: GitHub repo description)
    public let participants: [String: Bool]?    <-----  // Participants in the chat room, with the boolean indicating of they are currently active
    .... 
    public let githubType: String      // Type of the room.
    public let tags: [String]          // Tags that define the room.
}

A roomSignal can get me the currently selected room from which I can retrieve the active participants, as such :

     roomSignal
            .compactMap { $0.participants }     // participants for the room given by the signal
            .map { $0.filter { $0.value } }            // we filter only the active participants

But if I want to transform the resulting signal into a signal of array of Users (retrieved from an API call to a third party backend), so that I can display the list of users in the room, then I always hit a snag :

     roomSignal
            .compactMap { $0.participants }     // participants for the room given by the signal
            .map { $0.filter { $0.value } }            // we filter only the active participants
            .flatMapLatest { seq -> Signal<[Participant], ApplicationError> in
                seq
                    .{ User.me(with: $0.key).response(using: client) }
                    .?????
        }

So I am now left with a sequence (dictionary) in the flatMapLatest that will be transformed into an array of signals, when, what I want is a signal of sequence.

So :

Signal<[Participant], ApplicationError>
vs.
[Signal<Participant, ApplicationError>]

Note : using a combination of .flattenElements() and .collect() certainly makes things a lot easier. However, as mentioned in issue #209 , that will not work unless I prematurely finish the roomSignal -- of course, I might actually be doing something really stupid (wouldn't put that past me!) and then using .flattenElements() and .collect() would be the way to go !

Solution

If we extend Sequence to add a toSignal() method, with a transform function, that could potentially make things a lot smoother :

extension Sequence {
    public func toSignal<ElementOfResult>(_ transform: @escaping (Iterator.Element) -> Signal<ElementOfResult, Error>) -> Signal<[ElementOfResult], Error> {
        
        return Signal { observer in
            
            var collect: [ElementOfResult] = []
            self.forEach { transform($0).observeNext { result in collect.append(result) } }
            observer.receive(collect)
            return observer
        }
    }
}

I am just not sure that this is the right approach. Is there a better way to solve this problem ?

@npvisual
Copy link
Contributor Author

npvisual commented May 5, 2020

Or maybe we just need a flatMapElement() !

@srdanrasic
Copy link
Contributor

srdanrasic commented May 6, 2020

There is an initializer of Signal that takes a sequence of signals and flattens them into one signal. That would give you Signal<Participant, ApplicationError> where each participant is emitted as a new event. You could then reduce that into an array.

Signal(flattening: sequenceOfSignals, strategy: .concat)
    .reduce([]) { $0 + [$1] } 

This will work correctly only if each of the signals from the sequence of signals emits just one event (participant) and then completes. You could do sequenceOfSignals.first() to ensure that.

Using .concat strategy ensures order because it observes inner signals sequentially. If you don't care about order, you could use .merge strategy and then all inner signals would be started concurrently, producing the final result faster (usually).

@npvisual
Copy link
Contributor Author

npvisual commented May 6, 2020

Thanks for your response !

Yes, I found that yesterday, while looking through the code, and tried to use it. However I couldn't manage to make the function definition work with the Error protocol. But I think I got it working. So I combined it with your suggestion of using reduce (was using collect but it wasn't working).

extension Sequence {
    public func toSignal<ElementOfResult, E: Error>(_ transform: @escaping (Iterator.Element) -> Signal<ElementOfResult, E>) -> Signal<[ElementOfResult], E> {
  
        return Signal(flattening: self.map(transform), strategy: .concat).reduce([]) { $0 + [$1]}
        
    }
}

So I can now use it inside the flatMapLatest.

Unfortunately, it only works every now and then, but mostly fails : the API calls get discarded (cancelled request) before they get a chance to be collected. So I need to figure this out...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants