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) =&g
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:
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.
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'.
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.
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
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
.
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.