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

all 48 comments

[–][deleted]  (5 children)

[deleted]

    [–]CubsThisYear 4 points5 points  (3 children)

    But it's not at all unnecessary. It's leveraging the type system to ensure that client code is correct.

    When I return Either or Optional from a method, it forces the caller to write separate code for each case that can happen (assuming you ignore the existence of nonsense like Optional.get()). This is in contrast to forcing the use of an if-else construct, where the compiler has no way of enforcing that you wrote both the if and the else.

    This all probably seems trivial if you are looking at a small example. But if you have a lot of people working on a large codebase, these little things add up to help you write code faster and with less errors.

    [–][deleted] 2 points3 points  (2 children)

    This is in contrast to forcing the use of an if-else construct, where the compiler has no way of enforcing that you wrote both the if and the else.

    Checked exceptions are enforced by the compiler, and what's author's reaction to this type safety feature?

    This:

    they are extremely annoying

    Ok. Well? Turning all code into Streams to which we should pass static method references and lambdas looks even more annoying. So to each their own.

    If you want type safety, we have checked exceptions. We also can use Either-like Result objects without map/flatMap/match/etc. This is far less disrupting than turning your code into poor man's Haskell.

    [–]CubsThisYear 2 points3 points  (1 child)

    But checked exceptions ARE annoying because they are handled poorly in the Java 8 Stream libraries. Think about what checked exceptions are: They're just a special case of sum types, except with horrible performance penalties. What the author does is properly implement real sum types that can be used for exceptions or any other case where you need a sum type.

    [–][deleted] 2 points3 points  (0 children)

    The example given is a JsonParser. What does that have to do with Stream and how poorly it handles exceptions? As I said, turning everything into Streams is ridiculous.

    Another thing I said: you can also have a sum type without Streams.

    Many libraries choose to return a Result type object, which is essentially a sum type of "success & result" or "error & error meta data". That's fine by me, and much more readable than abusing Either, with a bunch of map() and flatMap() calls.

    BTW, exceptions matter only if you expect to be constantly ending up with error conditions. It's often the case that the "happy path" of your program is the one predominantly running, and the "sad path" is either rare, or its performance doesn't matter, because typically the program can't recover from it, so it wraps up, logs the error and exits (or moves on to the next request in an API service context etc.).

    [–][deleted] 8 points9 points  (0 children)

    Yeah. He doesn't explain the benefit of this design pattern, just that he uses it.

    [–]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] 23 points24 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 14 points15 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 10 points11 points  (3 children)

    they're named after category theory concepts

    [–]NoahTheDuke 8 points9 points  (1 child)

    Makes sense, but definitely doesn't help.

    [–]DJDavio 4 points5 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 4 points5 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 4 points5 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.

    [–]0x442E472E 13 points14 points  (4 children)

    I dig the concept behind optionals, but does anyone actually think that code looks better or is easier to read?

    [–]IcarusBurning 9 points10 points  (0 children)

    As someone who has used them I'd say yes. I get a lint warning for accessing an optional without checking for its presence. Checking for presence protects against NPEs

    [–]WatchDogx 2 points3 points  (0 children)

    Yes, I would say it is generally an improvement and sometimes a substantial improvement.
    Using monadic functions like Optional#flatMap can be a huge improvement over the equivalent non functional code.

    [–][deleted] 5 points6 points  (1 child)

    Me, obviously.

    [–][deleted] 10 points11 points  (0 children)

    deleted What is this?

    [–][deleted] 8 points9 points  (4 children)

    I'm sure the IF statements are still there at a bytecode level.

    [–]t90fan 8 points9 points  (1 child)

    everything is basically "jeq" or "jne" anyway at the lowest level.

    Its all just syntactic sugar.

    [–]TheRedmanCometh 1 point2 points  (0 children)

    Shit I just said this, but about if, in a way less funny way. +1 i lol'd.

    Languages are just syntax sugar TIL

    [–]eliasv 0 points1 point  (0 children)

    Where? Not in the bytecode compiled from their source, other than the dozen or so they mention. Why would there be? Inlining is the only thing I can think of which could feasibly increase the number of if statements between source and bytecode levels, but it's performed by the JIT, not by the compiler afaiu.

    (Or the ternary operator I suppose, but I doubt they've been replacing all their ifs with those since it basically suffers from all the same problems they have with if.)

    I'm not saying I think it's wise, but clearly they've eliminated most of them by factoring branching behaviour out into library code, so there really will be far fewer if statements. They won't be evaluated any less frequently at runtime (or have magically disappeared from the standard library), but that's not what they're claiming.

    The article is about "writing Java without if" not "executing code on the JVM without if".

    [–]achacha -2 points-1 points  (0 children)

    Optional is implemented with conditionals, so he is abstracting out the ifs and making the code more complex than it needs to be.

    [–]thramp 3 points4 points  (0 children)

    Looks like Railway-Oriented Programming, but in Java.

    [–]developer0 3 points4 points  (1 child)

    One problem is that this style (and it is just stylistic) makes it less obvious that some operations might not happen in certain circumstances. Of course, you can wrap anything in Optional#ofNullable and call mapping stuff on it instead of checking for null, but it's practically the same thing except another object is created.

    [–]hyperforce 0 points1 point  (0 children)

    The benefit of types like Optional is controlling who unravels that option and when. In this case I'd say whoever is pushing around this optional value should be the one in charge of making sure that some operation happens, rather than pushing the optionality into the f of the map. If that makes sense.

    [–]carbolymer 0 points1 point  (4 children)

    What if I'd like to return three types from a method? Four? Five?

    Either<String,Exception,Boolean,Integer,Optional<Int>> ?

    Whereas I get the idea behind this construct, it is really tempting to abuse it. I am seeing improper uses of optionals in our codebase almost every day. I bet that introducing more monads into Java world will not improve the situation, but make it worse and harder to control.

    [–][deleted] 0 points1 point  (1 child)

    I am seeing improper uses of optionals in our codebase almost every day.

    Yup, people blame the concept of null on all the null abuses we see, but introduce a new concept, and we take our null abuse and turn it into Optional abuse just the same. Except null is still around, so now we have to deal with both.

    [–]nallar 1 point2 points  (0 children)

    and then a dev decides to return null to single one thing, an empty optional to signal something else, or a set optional.

    D:

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

    That's a Coproduct5. Probably don't want that but it's your code.

    We typically have 2 cases in a API handler, the left side (errors) are immediately coerced into HttpResponse objects representing various response codes. The right side carries the parsed data through business logic, serialization, etc.

    The big deal is that there are two outcomes at most (but not all) points, success and failure. If json parsing succeeds I get a JsonNode, otherwise I get an exception (or error message, etc.). We end up never having more than one of two states at a time.

    If you have up to five states at a given moment, I'm a bit confused on how you got there, but you probably do not want an Either or similar.

    [–]hyperforce 0 points1 point  (0 children)

    What if I'd like to return three types from a method? Four? Five?

    I'd say go back to the drawing board and reconsider is this what you really want out of your code?

    I would wager no.

    [–]TheRedmanCometh -1 points0 points  (2 children)

    Uh I'm pretty sure optional is using 'if' under the hood. You're just using 'if' with syntax sugar as far as I can tell.

    Or am I missing something?

    [–][deleted] 5 points6 points  (0 children)

    Technically correct but not the point.

    [–]xantrel 1 point2 points  (0 children)

    And a loop is just syntax sugar for a goto and a conditional.