Scala: how to understand the flatMap method of Try

2019-04-05 06:39发布

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?

6条回答
Rolldiameter
2楼-- · 2019-04-05 07:15

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.

查看更多
Melony?
3楼-- · 2019-04-05 07:22

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'.

查看更多
爷的心禁止访问
4楼-- · 2019-04-05 07:27

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

查看更多
够拽才男人
5楼-- · 2019-04-05 07:31

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.

查看更多
Ridiculous、
6楼-- · 2019-04-05 07:36

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.

查看更多
The star\"
7楼-- · 2019-04-05 07:38

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.

查看更多
登录 后发表回答