Reactivecocoa: Why do you prefer flattenMap: over map:?

Created on 25 Apr 2013  Â·  13Comments  Â·  Source: ReactiveCocoa/ReactiveCocoa

Hi guys,

There is a famous example from the RAC announcement blog post:

[[[[client loginUser] flattenMap:^(User *user) {
    return [client loadCachedMessagesForUser:user];
}] flattenMap:^(NSArray *messages) {
    return [client fetchMessagesAfterMessage:messages.lastObject];
}] subscribeCompleted:^{
    NSLog(@"Fetched all messages.");
}];

It looks like the same effect would be from code like this:

[[[[client loginUser] map:^(User *user) {
    return [client loadCachedMessagesForUser:user];
}] map:^(NSArray *messages) {
    return [client fetchMessagesAfterMessage:messages.lastObject];
}] subscribeCompleted:^{
    NSLog(@"Fetched all messages.");
}];

Could you please explain why do you prefer flattenMap: over map:? Maybe there is some less visible pitfall? Is it correct at all to use map: for such cases?

Thanks in advance!

question

Most helpful comment

Use -map: whenever you want to map from a value to another value. There is no difference between a "value" and a "signal" in this context. Values are just objects that can be sent on a signal, and signals can send signals as their values. They aren't treated any different from other objects in the context of -map:.

In other words, any of the following mappings of input values to output values are valid:

| input value | output value |
| --- | --- |
| non-signal | non-signal |
| non-signal | signal |
| signal | non-signal |
| signal | signal |

On the other hand, -flattenMap: is used whenever you want to map from an input value to an output value that is a signal, _and you want that output signal to be flattened into the receiver's stream_. The input value being mapped could be a signal, but it doesn't have to be. But the output value returned from the block _must_ be a signal, and furthermore, that signal's values will be -flattened into the receiver's stream.

| input value | output value |
| --- | --- |
| non-signal | signal |
| signal | signal |

This "flattening" means that the subscriber to the signal that is being -flattenMap:-ed will receive the values sent by the signal returned from -flattenMap: as if it had subscribed to that signal (because that is exactly what -flattenMap: does).

The difference is that if you were to -map: an input value (regardless of whether it is a signal or not) to an output value that is a signal, the subscriber is not automatically subscribed to that output signal. It just receives the signal as it would any other value.

Two related operations that might be worth learning more about are -flatten (not to be confused with -flatten:) and -concat. If you understand -map: and -flatten, then you can understand -flattenMap:.

All 13 comments

The block passed to -flattenMap: must return a signal. For -map:, the return value from the supplied block is wrapped in a signal using -[RACStream return:].

Your second example with -map:, would create a signal of signals. Which you may or may not want.

Got it, thank you very much.

So in general. Use -map: whenever you want to map a value to a value. And use -flattenMap: whenever you want to map a value to another signal. Right?

@hfossli the second case depends on what behavior you espect. Trasnformation Value -> Signal could be done via -map:] switchToLatest]. But the logic differs.

Use -map: whenever you want to map from a value to another value. There is no difference between a "value" and a "signal" in this context. Values are just objects that can be sent on a signal, and signals can send signals as their values. They aren't treated any different from other objects in the context of -map:.

In other words, any of the following mappings of input values to output values are valid:

| input value | output value |
| --- | --- |
| non-signal | non-signal |
| non-signal | signal |
| signal | non-signal |
| signal | signal |

On the other hand, -flattenMap: is used whenever you want to map from an input value to an output value that is a signal, _and you want that output signal to be flattened into the receiver's stream_. The input value being mapped could be a signal, but it doesn't have to be. But the output value returned from the block _must_ be a signal, and furthermore, that signal's values will be -flattened into the receiver's stream.

| input value | output value |
| --- | --- |
| non-signal | signal |
| signal | signal |

This "flattening" means that the subscriber to the signal that is being -flattenMap:-ed will receive the values sent by the signal returned from -flattenMap: as if it had subscribed to that signal (because that is exactly what -flattenMap: does).

The difference is that if you were to -map: an input value (regardless of whether it is a signal or not) to an output value that is a signal, the subscriber is not automatically subscribed to that output signal. It just receives the signal as it would any other value.

Two related operations that might be worth learning more about are -flatten (not to be confused with -flatten:) and -concat. If you understand -map: and -flatten, then you can understand -flattenMap:.

Great read. Thanks. This makes sense!

@jspahrsummers & @joshaber I have to say that @erikprice answer is spot on, and could well be used as a helper link here. It sure did help me understanding better what's happening with -map: -flattenMap:.

Pull requests welcome! :wink:

Wow... Thanks @erikprice for this explanation... It actually blew my mind once I saw it so clearly. Thanks!

A very interesting explanation is in this article http://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1

[[[self.signInButton
   rac_signalForControlEvents:UIControlEventTouchUpInside]
   flattenMap:^id(id x) {
     return [self signInSignal];
   }]
   subscribeNext:^(id x) {
     NSLog(@"Sign in result: %@", x);
   }];

This maps the button touch event to a sign-in signal as before, but also flattens it by sending the events from the inner signal to the outer signal.

And a very good post on map vs flattenmap Functor and Monad in Swift

Very cool @onmyway133 . I've never used flattenMap like that. Thanks for sharing

What do you mean with: _map from an input value to an output value that is a signal, and you want that output signal to be flattened into the receiver's stream._

map maps discrete time value one-to-one.

flatMap is essentially map-then-flatten — It first maps a value to a signal of U values. This means the resulting signal would become a signal of signals of U values. Then it would flatten the signal of signals of U values into just a signal of U values, based on the strategy you choose (concat, merge or switchToLatest).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Lewion picture Lewion  Â·  4Comments

porridgec picture porridgec  Â·  3Comments

gabro picture gabro  Â·  5Comments

simonxcheng picture simonxcheng  Â·  6Comments

v-silin picture v-silin  Â·  4Comments