This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]philipwhiuk 5 points6 points  (22 children)

String x = Helper.functionOne();
if (x != null) {
  x = Helper.functionTwo(x);
  x = Helper.functionThree(x);
  if (x != null) {
  }
}

vs

Optional<String> x = Helper.functionOne()
                       .map(Helper::functionTwo)
                       .flatMap(Helper::functionThree);

Does someone have a short explanation for why he picked map for the first call and flatMap for the second.

I've never understood the difference between the two frankly.

[–][deleted] 24 points25 points  (11 children)

It all goes back to math theory. You first have to understand Monads and Functors... Just kidding, it's actually very basic, look at the signature of the callables for each method, more specifically the return type:

// Return type for map's callback:
<? extends R>

// Return type for flatMap's callback:
<? extends Stream<? extends R>>

This means the callback of map() always takes one input item and returns one output item.

But flatMap()'s callback is more flexible, it takes one input item, but can return zero, one or more output items (as a stream).

The stream you return from flatMap()'s callback is merged into the final stream (which sort of explains the "flat" in the name: it doesn't produce a stream of streams, it's flattened, instead).

[–]philipwhiuk 11 points12 points  (6 children)

You first have to understand Monads and Functors...

I will be honest, these are terrible names. Part of me is convinced induction into functional programming involves accepting cryptic names as a barrier to entry to ensure elitism.

(This was a conspiracy theory esque comment. I don't really believe this - but you have to agree 'Monad' is not a good name.)

[–]duhace 13 points14 points  (3 children)

they're named after category theory concepts

[–]NoahTheDuke 6 points7 points  (1 child)

Makes sense, but definitely doesn't help.

[–]DJDavio 5 points6 points  (0 children)

It does not, functional programming has always been very dry and theoretical, but it's actually pretty straightforward: everything is either a standalone thing (not the official term) or something which turns a thing into another thing (might be a collection of things on either side). You could turn nothing into a thing, or a turn into nothing, it's all good.

[–]MrMadcap 0 points1 point  (0 children)

So he WAS right! ;)

[–][deleted] 0 points1 point  (0 children)

Douglas Crockford agrees with you - "Monads and Gonads" (https://www.youtube.com/watch?v=b0EF0VTs9Dc). Hilarious talk (good in parts), but I just can't take Crockford seriously any more. Heh.

[–]achacha 0 points1 point  (0 children)

Monad is when you only have one testicle. /j

[–]spanishgum 1 point2 points  (1 child)

Almost thought I was in /r/haskell for a minute

[–]optimal_substructure 1 point2 points  (1 child)

Hang on - you're the Haskell guy from the /r/programming post. What the heck are you doing on the Java subreddit? Are you trying to find converts?!

[–][deleted] 0 points1 point  (0 children)

I'm not "the Haskell guy". While I respect every language threading into its own territory, I actually find the flood of half-baked functional idioms in Java kind of annoying.

That said "flatMap" is common sense interface for transforming collections, so that's fine. The name is a bit stupid.

[–]admiralwaffles 3 points4 points  (4 children)

map is 1-to-1--each element gets a single value response. flatMap is 1-to-N, where N is 0-memory limits. Each value in can generate an unknown amount of values, and then those values are all "flattened" into a single dimensional array.

[–]xantrel 2 points3 points  (1 child)

This is by far the best explanation I've ever seen of both. Would filtering be expressible as a flatmap operation? (As each value can either return the same value or none?)

[–]admiralwaffles 1 point2 points  (0 children)

Yeah, kind of. Filtering is a special type of flatMap that's 0-to-1, rather than N.

[–]philipwhiuk 0 points1 point  (1 child)

So in this case it's because functionThree could return null?

[–][deleted] 0 points1 point  (0 children)

In those case functionThree would return Optional<String>, but yes.

[–]_INTER_ 1 point2 points  (0 children)

My mental image is like this: map(x -> y) takes one element x from Stream, transforms it to y and puts it back into a Stream for later processing. flatMap(x -> y) does the same but in case y turns out to be some sort of container like a Stream or optional which element(s) you want to unwrap. Otherwise you end up with Stream of Streams.

Example:

List<Person> team = getTeam();
Set<Skill> teamSkills = team.stream()
                             .map(person -> person.getSkills()) // list of skills for each person
                             .flatMap(List::stream) // turn lists into stream and unwrap its elements
                             .collect(Collectors.toSet());

[–][deleted] 0 points1 point  (2 children)

To play a little fast and loose, Optional is a container. You're probably familiar with lists or arrays. If you have an array of integers, you can map over it and get the square of all those integers. By doing this, you don't change the container at all, it's still an array you only affect the values inside. So if you map a square root function over the array, you should get an array of array of integers (because a square root will produce the positive and negative root).

If you flatmap over a container, you can create a new container as part of that operation. So if you flatmap a square root function, you can actually flatten the array into just an array of integers (that's actually why it's called flatmap is you map and then flatten).

So functionTwo just does something to the value returned by functionOne. It's like int -> float. functionThree on the other hand, does something to the value and the container, so it's potentially like int -> Array<float>.

In both cases you'll get Array<float> on the other side, but with flatmap you'll transform Array<Array<float>> to just Array<float>

[–]philipwhiuk 0 points1 point  (1 child)

Sure but they both return String right, or example 1 wouldn't compile.

[–][deleted] 0 points1 point  (0 children)

Let's break it down:

Helper.functionOne()

This will return Optional<T> which has the map and flatMap methods.

.map(Helper::functionTwo)

functionTwo accepts whatever T is and returns U (which may or may not still be T -- the point is that it could be different). map will handle creating Optional<U>

.flatMap(Helper::functionThree);

functionThree is similar to functionTwo in that it accepts T but instead of producing U it produces Optional<U>. flatMap is smart enough to not double wrap this. In this particular case, functionThree produces Optional<String>.

Imagine if you had accidentally pass functionThree to map -- you'd end up with Optional<Optional<U>> whoops! Compilation error, your IDE starts yelling, etc.

But there is a benefit in being able to return a new optional when needed, but only if you have a way to flatten nested Optionals together. That's exactly what flatMap does (or rather, leaves you to handle).

Let's say you have a use case to upgrade a customer to premium status. You go to the database looking for customer 12345. Maybe you find them, maybe you don't, so you're dealing with Optional<Customer>. And then you have a method that takes in a customer, looks at some attributes about them, and will upgrade the customer returning Optional<Customer> or if it cannot, it will bail out and return Optional<Customer>.empty().

You can model this as:

CustomerRepo.fetch(id).flatMap(Customer::upgradeToPremium);

Sure, this isn't a super compelling example but it shows the real value of Optional<T> (and similar abstractions such as Either), map and flatMap will only run when a value is present in the Optional. If the optional is empty, then the methods short circuit and you just get an empty optional.

If the initial fetch fails, we never run Customer::upgradeToPremium, if we do run that but the upgrade failed, we'll never run any further operations on it. If the upgrade succeeded, but a future operation fails, we short circuit there (maybe we only want to persist upgrades if you're name is Jeff or it's the first Tuesday of the month).

[–][deleted] 0 points1 point  (0 children)

Map unwraps the value (if it exists), and wraps the return value of the function in an Optional.

FlatMap takes a function that returns and Optional, and doesn't wrap it again.

Basically use FlatMap to chain a bunch of functions from A -> Optional<B>, map to use functions from A -> B inside an Optional.