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?
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 functionf
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 nestedTry[Try[U]]
.You can apply the same 'flattening of nested structure' concept to collections as you mention.
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'.
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 eitherSuccess
value ofFailure
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.
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 invokingf(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 avalue
inside (more generally a monad):flatmap
has an argument which is a function convertingSuccess
intoTry
=Success(value)
ORFailure(exception)
. Afterf(value)
is applied, the result is already aTry
. The "flatten" part is a trivial/null operation: iterating over this function result would give just one entry, henceTry
/Success
/Failure
don't even need to implementIterable
). Doesn't wrap additional layers ofSuccess
/Failure
, and so returns a "flat"Try
.I.e. The "flat" part means it doesn't cascade
Success
/Failure
wrappers, just as a sequence'sflatmap
doesn't cascade sequences in a (value tree) hierarchy.this is different to
map
, whose argument is a function convertingSuccess
into an arbitrary typeU
; afterf(value)
is applied, map must add an additional layer of newSuccess
/Failure
wrapping around thevalue
/exception
.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:
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:
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.