12

The flatMap method of the Success is implemented like this:

  def flatMap[U](f: T => Try[U]): Try[U] =
    try f(value)
    catch {
      case NonFatal(e) => Failure(e)
    }

I kinda understand what this method is doing, it helps us to avoid writing a lot of catching code.

But in what sense is it similar to the regular flatMap?

A regular flatMap takes a sequence of sequences, and put all the elements into one big "flat" sequence.

But the flatMap method of Try is not really flattening anything.

So, how to understand the flatMap method of Try?

Cui Pengfei 崔鹏飞
  • 8,017
  • 6
  • 46
  • 87
  • Search for monads/monoids pattern. flatMap transform original A[T] to A[U]. Option, Try, Future it do not need to be converted/flatten. See very old document about [monads](http://lampwww.epfl.ch/~emir/bqbase/2005/01/20/monad.html) – Andrzej Jozwik Nov 25 '13 at 14:35
  • 1
    The methods `map` and `flatMap` are not about sequences, they are about monads. There are many monads that have nothing to do with collections of any sort. `Try` and `Future` are examples of these. (While `Try` is not technically, strictly a monad, it's close enough for most purposes.) – Randall Schulz Nov 25 '13 at 14:45
  • 3
    However, my understanding is that flatMap is called that because of the flattening it does do on sequences. So at a minimum there's some cognitive dissonance when applying it to non-collection monads. It's reasonable to ask for an explanation and an answer other than "it's not soup, sir, it's broth" would be good (c.f foreach, which has the same dissonance). – The Archetypal Paul Nov 25 '13 at 15:10
  • 1
    @Paul: the flattening metaphor also fits the stricter Monad definition of wrapping computations: it transforms a nested structure of transformations into a flat one. You could substitute it for 'unwrapping' without much loss of significance as well. – danielkza Nov 25 '13 at 15:55
  • And how about 'foreach'? :) Seriously, that's the kind of explanation that helps. Thanks. – The Archetypal Paul Nov 25 '13 at 16:55

6 Answers6

11

Without entering into monads, instead of thinking about it in terms of collections, you could think of it in terms of structures (where a collection becomes a structure with many entries).

Now, take a look at the signature of Try.flatmap (from your post):

def flatMap[U](f: T => Try[U]): Try[U] the function f transforms T into a Try[U] in the context of Try[T].

In contrast, imagine the operation were 'map', the result would be:

def badMap[U](f: T => Try[U]): Try[Try[U]]

As you can see, flatmap is 'flattening' the result into the context of Try[T] and producing Try[U] instead of the nested Try[Try[U]].

You can apply the same 'flattening of nested structure' concept to collections as you mention.

maasg
  • 37,100
  • 11
  • 88
  • 115
  • 1
    I was already afraid the accepted answer would be some complex monad theory, but instead it was the simple and clear one I immediately thought of! – Erik Kaplun Jan 30 '14 at 12:37
5

You may consider Try[T] as similar to a collection of only one element (like Option[T]) .

When the "sequence of sequences" is "only one sequence", map and flatmap are almost similar. Only difference being the signature of the function.

No flattening is required in this case.

Shyamendra Solanki
  • 8,751
  • 2
  • 31
  • 25
2

I found Dan Spiewak's "Monads Are Not Metaphors" very helpful in getting my head around monads. For folks starting from Scala (like me) it's far easier to grasp than anything else I've found - including Odersky's writings. In reading it, note that 'bind'=='flatMap'.

Ed Staub
  • 15,480
  • 3
  • 61
  • 91
1

As you can read in A Tour of Scala: Sequence Comprehensions: "In scala every datatype that supports the operations filter, map, and flatMap (with the proper types) can be used in sequence comprehensions." In fact this means you can threat it like a monad.
And flatMap for monad have signature like this:

def flatMap(f: A => M[B]): M[B]

All collections in scala have monadic interfaces, so you can look at monadic operations in that narrow scope as operations on sequences. But that is not the whole story. In case of some monads looking on them as on collections is more confusing than helpful. Generally flatMap applies a transformation of the monad "content" by composing this monad with an operation resulting in another monad instance of the same type. So you can look at monads in at least two ways:

  • Monad is some kind of collection (or box holding something) and elements of that collection are "content".
  • Monad is some kind of context and elements of monad are just some computations made in that context.

Sometimes it's easier to think about monad as collection, sometimes it's easier to think about it as context. At least for me. In fact that both approaches are interchangeable, i.e. you can look at lists (collections) as nondeterministic computations which may return an arbitrary number of results.

So in case of Try it could be easier to think about it as execution context, with two states, Success and Failure. If you want to compose few Tries then and one of them is in Failure state then whole context becomes Failure (chain is broken). Otherwise you can do some operations on the "content" of that Tries and the context is Success.

Piotr Kukielka
  • 3,792
  • 3
  • 32
  • 40
1

A regular flatMap takes a sequence of sequences, and put all the elements into one big "flat" sequence.

Slight correction:

A regular flatMap takes a sequence (more generally monad) , has an argument which is a function converting an element into a sequence (monad), and returns a "flat" sequence (monad).

For comparison purposes, the gory substeps mentioned here :). The flatmap method iterates over input sequence invoking f(element), but creates a singular new result sequence. The "flatten" part is applied after each function argument application, f(element) - it does a nested iteration over the resulting sub-sequence, yielding each entry in the singular result sequence.

The equivalent for Success, with a value inside (more generally a monad):

  • flatmap has an argument which is a function converting Success into Try = Success(value) OR Failure(exception). After f(value) is applied, the result is already a Try. The "flatten" part is a trivial/null operation: iterating over this function result would give just one entry, hence Try/Success/Failure don't even need to implement Iterable). Doesn't wrap additional layers of Success/Failure, and so returns a "flat" Try.

    I.e. The "flat" part means it doesn't cascade Success/Failure wrappers, just as a sequence's flatmap doesn't cascade sequences in a (value tree) hierarchy.

  • this is different to map, whose argument is a function converting Success into an arbitrary type U; after f(value) is applied, map must add an additional layer of new Success/Failure wrapping around the value/exception.

Community
  • 1
  • 1
Glen Best
  • 22,769
  • 3
  • 58
  • 74
0

A regular flatMap takes a sequence of sequences, and put all the elements into one big "flat" sequence

It would be fair to replace word sequence to monad here, because this operation doesn't relate only to collection, actually collections are also monads. Think of Try as collection that can contain either Success value of Failure

maks
  • 5,911
  • 17
  • 79
  • 123