
Java Without If - tretiy3
http://ashtonkemerling.com/blog/2017/01/26/java-without-if/
======
mabbo
Optionals are a huge step forward for Java, even if they aren't perfect. They
let you write interfaces that say "I might not have an answer and if you don't
deal with it that's your problem". That's important.

That they also allow mapping, filtering, etc, isn't about 'removing ifs' or
'hiding ifs' so much as they are about writing more readable code, imho.

    
    
      Optional<Foo> myValue = gateway.callThatApi(...)
      return myValue.filter(Utils::isNotTooShabby)
                    .map(this::mapToCoolerType)
                    .map(getDecorator())
                    .orElseThrow(new TotallyBlewItException())
    

Is this perfect and beautiful? Nah. But it's better than the 20 lines of Java
7 code I'd need to do the same thing. I'm able to write simple predicates and
mapper functions as class variables, dynamically if I want, and call them in
order as I like. It's short, it's descriptive rather than prescriptive. It
isolates what I want from how I do it.

Debugging is annoying, yes, but I think there's hope that a good pattern for
it will be figured out by the community.

~~~
gravypod
Why is Optional<> better then throwing an exception or returning a null?

The way I see it it's like this:

    
    
        Optional<Integer> num = getSomeRiskyNumber();
        if (!num.isPresent())
            return ... code to bubble up a blank optional
     

vs

    
    
        Integer num = getSomeRiskyNumber();
        if (num == null) 
            ... throw exception or return null
    

I get that the author has adopted a more functional programming methodology
for dealing with their data but for some tasks this isn't acceptable. To just
return 1 value you've allocated at least one object (Optional) and make at
least 2 function calls on it (isPresent() and get()).

You get `if (value == null)` for free. Throwing exceptions is very heavy and
I'd place Optional<> above that but there isn't any way to signify the error
that you acctually got. You'd need to make an Optional<Maybe<T>> where
Maybe<T> supports error/exceptions. Maybe Maybe<T> holds data and you can
extend it with BadMaybe<T> who extends exception or something so you can put
that in instead of your value to signal your exception.

I don't see the benifit. Maybe I'm just crazy but `if (v == null)` has all the
features Optional has for less of an overhead and less cognitive load. (If
you're afraid of NPEs then just document all the return states of your methods
and use @nullable to show when you need to check. IIRC IntellJ catches that
kind of mistake).

~~~
mabbo
Simple answer: never return null.

If something is wrong, throw an exception. If it's the kind of error that must
be handled, throw a checked exception. If there's no value to return, return
Optional.empty.

The code receiving that Optional<Foo> is now free to do what the author is
suggesting: map, filter, all without worrying if the value is actually there
or not. The code is cleaner, easier to read, etc.

But no one should ever have to check for null anymore.

~~~
gravypod
" _If something is wrong, throw an exception._ "

That is sometimes just not an option performance wise yet.

" _If it 's the kind of error that must be handled, throw a checked exception.
If there's no value to return, return Optional.empty._"

Again, _why_ is Optional.empty() better then null? What makes it better? What
do you get from throwing an exception? What is the benifit. You can't just say
"my way is better" when we have years and years of Java development flying
contrary to your statement. What has changed that makes null a non-viable
practice?

" _The code receiving that Optional <Foo> is now free to do what the author is
suggesting: map, filter, all without worrying if the value is actually there
or not. The code is cleaner, easier to read, etc._"

The code being cleaner is a subjective, or at least extremely difficult to
prove, statment. For instance

    
    
        List<Integer> nums = getNumbers();
        if (nums == null) {
            S.o.p("Failed to load numbers");
            return;
        }
        for (Integer i : nums)
            System.out.println(i);
    

Is far better then

    
    
        Optional<List<Integers>> nums = getNumbers();
        if (!nums.isPresent()) {
            S.o.p("Failed to load numbers");
            return;
        }
        nums.forEach(S.o::p);
    

Or even better yet

    
    
        for (int i : nums)
            if (i < 10)
                S.o.p(i);
    

VS

    
    
        nums.filter((n) -> n < 10).forEach(S.o::p);
    

I don't think that's more readable. It think that's more compressed. HEre's
another example. Suppose we have a magical language that I'm sure you'll pick
up. It's a very compressed (or as you'd say expressive) language. This is that
same code written in it

    
    
        pa(i i nums < 10)
    
    

Which expands to "print all the ints `i` in nums that are less then 10" in
english. That's far less readable. It is more compressed. I don't think
compression is the goal of a language as much as it is a goal of a zip
program. We're supposed to be writing software, not liturature to submit to
the shortest story fully told contest. Readability is a function of correct
verbosity.

In my opinion. Just compressing your logic doesn't make it more readable. I
think some verbosity is needed to preserve simplicity.

" _But no one should ever have to check for null anymore._ "

I mean that just doesn't make sense. If you're suggesting that there are some
times the state of a program should never contain a null value is just
ridiculous. Some things are correctly modled by null and some things are also
too performance dependant to not use null.

I think some things benifit from using Optional<> but the case doesn't exist
to completely remove null. Even just by the creation of a new container object
wrapping your already expensive return object there exists a case for null to
exist.

Just saying "Don't do it it's bad" is not proof. Saying "the code is cleaner,
easier to read, etc" is not proof or even an example of a design that is
simpler to pickup and get going with. You'd have to write some code with the
Java/OOP paradigms and write a version (that is _feature complete_ ) with the
FP paradigms and see which is easier to understand for a new programmer. I'd
be hard pressed to belive that the FP implementation would be simpler. Maybe
to you and me but no to someone without the domain specific knowladge required
to understand what's going on. Even when I use map, zip, and list
comprehensions in my python code it scares off some of my coworkers.

~~~
ebola1717
In practice, your first example would be more likely to look like:

    
    
        public List<Integer> add2(File file) {
            List<Integers> nums = file.getNumbers();
            if(nums != null) {
                List<Integer> resultList = List<>();
                for ( i : nums) {
                    resultList.append(i + 2);
                }
                return resultList;
            } else {
                return null;
            }
        }
    

Compare that to:

    
    
        public Optional<List<Integer>> add2(File file) {
            Optional<List<Integers>> numsOpt = file.getNumbers();
            return numsOpt.map((nums) =>
                nums.map((i) => i + 2);
            );
        }
    

I find it hard to argue that the latter is worse.

~~~
logn

        public List<Integer> add2(File file) {
          List<Integer> resultList = new ArrayList<>();
          for (int i : ListUtils.emptyIfNull(file.getNumbers())) {
            resultList.append(i + 2);
          }
          return resultList;
        }

~~~
coldtea
And now you need to use a non-language builtin util everywhere. And this one
only works when working with arrays or array like objects...

It's amazing how far people will go to defend a bad practice, than even its
inventor called a 'billion dollar mistake'.

------
wellpast
This is just lipstick.

The real problem is branching - when reading code I have to think through two
conditional cases.

In this particular example (where you have to validate a client request), I
don't see a way out of branching. However I don't think this post has produced
the ideal:

    
    
        JsonParser.parse(request.getBody())
           .flatMap(Validator::validate)
           .map(ServiceObject::businessLogic)
           .flatMap(JsonGenerator::generate)
           .match(l -> HttpResponse.internalServerError(l.getMessage()),
                  r -> HttpResponse.ok(l));
    

The problem with this is that I have to think through branching all the way
through the data flow. However the only function that should branch is
validate, to prepare the request to meet the preconditions of the rest of the
data flow, all of which should be non-branching.

In other words, I should be able to read this part of the data flow without
thinking of branching:

    
    
       (generate-json (business-logic req))
    

So this I believe is objectively better:

    
    
       (if (valid? req)
          (generate-json (business-logic req))
          (generate-json (errors req)))
    

Yes, I've used an if. (If we don't like ifs we can easily get rid of it, of
course - but again our problem is branching not the if.)

Why is this objectively better? Because we now have to think about branching
wrt to the validation function ONLY. We've _minimized_ where branching
matters, and that's solving the core issue.

~~~
lkrubner
Likewise, using "match", I think the Clojure solution for FizzBuzz is very
elegant:

    
    
        (doseq [n (range 1 101)]
          (println
            (match [(mod n 3) (mod n 5)]
              [0 0] "FizzBuzz"
              [0 _] "Fizz"
              [_ 0] "Buzz"
              :else n)))
    

To my mind, this reads much more clearly than if I wrote a bunch of if()
statements.

~~~
iainmerrick
What makes it clearer? I think you're just more used to "match", whereas other
people are more used to "if".

The structure is almost identical. You even have an "else" clause!

~~~
village-idiot
:else is a convention in Clojure that has truthy keywords. You could replace
that with anything that evaluates to true, like :foo, or true, or 1, etc.

~~~
iainmerrick
But don't you see what I'm saying? I assume the order is important, and if the
else: (or whatever else you decide to use) were first it would match
everything and prevent the other cases from being used. So it's exactly
equivalent to an if/elif/else chain. The structure is the same.

~~~
aarongolliver
Things can be semantically equivalent but more elegant, easier to read, and
harder to make mistakes.

I think most function programmers are actually very familiar with the patters
found in Java. It's often one of the reasons they fell in love with more
functional styles of programming.

------
cjensen
I like functional program a lot, but that very first transform of a chain of
if's into a horrifying mess makes a pretty good case for using if. In the if
case, function2 and function3 were called identically, but in the functional
case suddenly things are inconsistent.

Is this a satire?

~~~
chc
They're called identically in the if case, but they're not _used_ identically.
One of functions isn't guaranteed to return a value, so it requires an "if x
!= null". flatMap basically says "this might be null." Part of the point here
is that it makes it explicit when something might return null and ensures it's
handled properly.

------
kbuchanan
I feel like the article took a surprising and unusual position - "We prefer
Java to Clojure now" \- but, then, instead of justifying that position,
instead showed how Optional lets you write more functional Java.

It's been a long time since I've written any Java, so I wonder, how is this
better than Clojure?

~~~
virmundi
Typing. I like Clojure. I've made a few simple libraries for it while
learning. What I don't like is making web service contracts in it. The lack of
typing makes the code hard to follow once you get past the first handler. Java
keeps types around.

~~~
raspasov
Checkout clojure.spec - you might find it very useful. The problem with types
is that they are only a static/at rest description of your data. For example,
it's a String or it's a Date.

But does that String contain @ character (checking for email)? Is this Date in
the future or in the past (validating a credit card form)? Types say nothing
about that. I'm not saying that types have zero utility, but in the vast
majority of my use cases compile-time type checking doesn't go very far.

~~~
virmundi
I don't like spec. I liked the other one (by Mars I think that actually checks
nested objects).

Perhaps I'm just stuck in my ways, but I like to be able to see in the code
I'm looking at what attributes or actions are applicable to this thing. With
Clojure I lose all of that. What is in this map? Well, I better print it out
to know.

Clojure's dynamic typing, and any dynamic language to me, is only useful if
you're never more that 2 stack frames away from your data's source. After that
it's a lot of documentation to make sure you don't cock it up.

~~~
escherize
Well, with sufficiently good spec definitions you can know (and generate
examples of) exactly what is expected to be "in this map".

Oh, spec also serves as documentation!

Maybe you'll like more now. :)

------
jankotek
Optional types are clumsy when compared to modern alternatives:

\- Optional does not protect from NPE, null is still allowed

\- It adds extra layer of complexity

\- some libraries use it, some do not. it is not enforced

\- extra typing, Java does not even have type inference and `val` declaration

\- `if` expression in java does not return a value, no pattern matching...
again far more typing

\- no support for chained call on several nullable fields

I use Kotlin for couple of years. It has nullability baked into type system
and enforced by the compiler. And it works with existing java libraries.

It feels like going back 15 years to Java 1.4, when I use Optional in Java8 or
Scala.

~~~
pkolaczk
Kotlin can't abstract over optional types. Scala Option is a monad which let's
you do plenty of cool and useful stuff that Kotlin can only dream of. Kotlin
solution is actually more complex, because it is baked into the language as a
special case with special syntax.

~~~
edem
Scala is still a baroque abomination which excels in nothing and the
advantages are shadowed by its warts. Kotlin is just Turbo Java.

~~~
pkolaczk
Why so angry? I pointed to one obvious limitation of Kotlin null handling
compared to Scala and you're attacking the whole language. Scala and Kotlin
both have their place and they are not direct competitors at all, because they
have different goals and address different groups of programmers, despite
Kotlin drawing actually a lot from Scala. It is actually quite funny, when
features in Kotlin are presented as making the language more productive, yet
the very same features in Scala are presented as an example of "Scala being
baroque abomination".

~~~
edem
What makes you think I'm angry? I thought that scala was an abomination long
before Kotlin came to existence. And I usually compare it to clojure which not
only tries but succeeds at being a functional language and on the expressivity
scale nothing gets near a lisp dialect. Your comment added no value and you
obviously despise Kotlin. From what I see in the city I live: 80% of scala
projects fail to deliver and never get to production. The older projects which
survive suffer from the slow compiler and the binary incompatibility. Not to
mention the arbitrary use of all the tacked-on features by scala devs which
makes streamlining code style frustrating from both sides.

~~~
pkolaczk
"What makes you think I'm angry?"

The wording you used and the fact that your post was a completely off-topic
opinion, just to attack the language you don't like. We're discussing various
null-safety approaches here, not opinions about which language is better. That
Kotlin can't treat optional types as any other monad (e.g. Either or Try
monads) unlike Haskell, Scala and probably half a dozen other languages is a
fact, not opinion. Also baking optional types into the syntax and the type
system makes them language more complex and this is also a fact.

> 80% of scala projects fail to deliver [citation needed]

------
ajkjk
Is there a non-traumatizing way to debug long chains of method calls? I had a
lot of trouble with this when I last used Java, a year or so ago.

~~~
ebola1717
\- Don't chain multiple methods in a line. One call per line. IDEs will
usually let you set breakpoints per line.

\- You can still use intermediate variables for readability

e.g. instead of

    
    
        nums.filter((n) => n % 2 == 0).map((n) => n + 2).foreach((n) => print(n))
    

do:

    
    
        nums.filter((n) => n % 2 == 0)
            .map((n) => n + 2)
            .foreach((n) => print(n))
    

or:

    
    
        List<Integer> evens = nums.filter((n) => n % 2 == 0)
        evens.map((n) => n + 2)
            .foreach((n) => print(n))

~~~
Tarean
I think java's problem here is how annoying it is to split these up. For
example your example completely split up in java vs haskell:

Java

    
    
      IntPredicate even = i -> i % 2 == 0;
      IntUnaryOperator add2 = i -> i + 2;
      UnaryOperator<IntStream> process = s -> s.filter(even).map(add2);
      for (int i in process.apply(numbers)) {
          System.out.println(String.valueOf(i));
      }
    

Haskell:

    
    
      printProcessed  = mapM_ print . process
        where process = map (+2) . filter even

~~~
pekk
It's even more concise in APL, assuming you know how to read APL.

------
amelius
Isn't this just pushing the "if" into the called functions? E.g.,

    
    
        x = something that might result in an exception
        x = f(x)
    

Now f has to check whether x contains an exception, and it should return that
exception in that case, and otherwise it should just apply the function to the
argument.

~~~
Sharlin
This is exactly what the flatMap (aka monadic bind) calls do. flatMap/bind is
sometimes called "a programmable semicolon", allowing one to customize what
happens when chaining operations.

~~~
trevor-e
Can you explain the "a programmable semicolon" part? I've tried and tried and
can't think of any way that makes sense to me. I use flatMap all the time but
still don't understand the expression.

~~~
cgag
I understand monads and don't think it's a helpful metaphor, just kind of cute
once you already understand what it's trying to explain.

------
beached_whale
As nice as it would be to have a method in java that cannot receive a null
argument like Result method( Type NOT NULL name ), it isn't there.

I fail to see much difference between an Optional has/doesn't have a value and
null. It's just paint and there is no insight into why there is no value.
Something like an Expected type that has either a value or the Error/Exception
is much more explicit and may let someone do something about it. At least then
the user of the method can choose what an appropriate action is with
knowledge. But optional and null are the same and give you no more information
than a result or that there is no result.

~~~
village-idiot
Why is map and filter nice on a list? Because the individual functions passed
in have no idea they're in the middle of a map or filter. Ditto with mapping
or filtering on Optional or Either. It composes better.

------
EdSharkey
Someone smarter than me can explain why and how, but I had also heard that
streams in Java can perform better than equivalent imperative code as well as
being null-safer.

This is because the standard library can forego memory allocation for
temporary data structures implied in expressions throughout the stream
statement. Also, the Java 8 VM can apply other aggressive optimizations to the
lambda functions to inline them.

~~~
mike_hearn
Sadly, no. Code written in this style will typically run more slowly, or be
equivalent _at best_. Streams can perform better if you make them parallel and
have lots of data as then you can more easily spread out over multiple cores
(which is the point of streams), but most cases aren't like that.

When you heavily use stream constructions, you're relying on the JVM to:

• Synthesise classes for the lambdas.

• Inline the map/filter/fold calls and then inline the lambdas too. The JVM
doesn't make any guarantees about inlining and may unpredictably bail out. If
inlining doesn't happen then profile pollution will kill off some of the other
optimisations the JVM does.

• Escape analyse any temporary objects created like iterators and then scalar
replace them.

• Try and do some loop fusion, but I'm not sure to what extent the JIT
compilers can do that.

This is a long list of complex and often fragile optimisations. If any of them
don't get applied then you end up with virtual method calls, objects being
created, poor cache utilisation etc. Sure objects that die young are cheap but
they aren't entirely free. It's still best to avoid them.

The reality is that writing traditional style code is going to be more
efficient or at least more reliably efficient than functional style code for
the forseeable future.

Note that the JVM has a much easier time of it when using Kotlin's support for
lambdas and functional programming because the inlining is guaranteed to be
done by the Kotlin compiler not the JVM, and that fixes a lot of issues with
profile pollution and unpredictable performance drops.

------
iopq
I did FizzBuzz in Rust without using ifs:

[https://bitbucket.org/iopq/fizzbuzz-in-
rust/src/bf4968973d73...](https://bitbucket.org/iopq/fizzbuzz-in-
rust/src/bf4968973d73137f0dfd07205d599bed30a788fa/src/lib.rs?at=master&fileviewer=file-
view-default)

~~~
vvanders
I'm more of a fan of the zip/cycle approach:

[https://play.rust-
lang.org/?gist=3fb51314d7df9249c9f774dde96...](https://play.rust-
lang.org/?gist=3fb51314d7df9249c9f774dde963873d&version=stable&backtrace=0)

~~~
bradmwalker
Python
[https://gist.github.com/bradmwalker/b300f36e9427f729b458](https://gist.github.com/bradmwalker/b300f36e9427f729b458)

~~~
iopq
I like this approach. The only thing I would criticize is that it uses '' as a
substitute for None.

So something like

    
    
        fizzes, buzzes, empties = str_cycle('Fizz', 3), str_cycle('Buzz', 5), str_cycle('', 7)
    

wouldn't work since it would print 7 instead of the empty string

------
slantedview
There are a few of these functional style programming APIs for Java. My
favorite so far: JavaSlang [1]. Would be interested to see how it compares to
the Lambda library mentioned in the article.

[1]: [http://www.javaslang.io/](http://www.javaslang.io/)

------
markelliot
Conceptually this seems to be a Java approach to Railway Oriented Programming
([http://fsharpforfunandprofit.com/rop/](http://fsharpforfunandprofit.com/rop/)),
which is pretty sweet.

------
edem
So what was the problem with Clojure? The article makes no connection between
the demonstration of the benefits of Either and why they ditched Clojure. As a
Clojure user I can only imagine that they did not have the competency (hiring
problems for example). Why don't you just pick up Kotlin and forget about
nulls altogether?

~~~
village-idiot
Try to up your reading comprehension before you question the competency of
others.

~~~
edem
Try to down with the sickness your brain has before commenting something
utterly useless and calling names. I was talking about the lack of competent
workforce.

------
SeriousM
The fact that you don't write "if" doesn't mean your aren't using it. Dann
clickbait.

------
fulafel
Inventing your own dialect of a language when none of the libraries support it
sounds like you are going to be writing a lot of wrappers or reimplementations
of things?

I would have really liked to hear what the argument for switching to Java is -
over staying on Clojure or switching to a language other than Java.

------
draw_down
I feel like, if you mean "if" say "if". As opposed to doing a little dance
that has the effect of "if true do this, otherwise do that".

~~~
nv-vn
One could argue that if you mean "map" use "map", not a little dance with a
for loop. I'm inclined to say that the same argument could he applied to "if",
but without seeing what their particular code looks like I don't know for
sure.

~~~
pekk
And if you are mapping not to a value but to the execution of a statement,
should that be map instead of for?

------
a3n
> Optional gives us the ability to say “if a value exists, apply this function
> to it” repeatedly. It also gives us the ability to chain successive calls
> ...

It sounds like a ternary operator to me.

~~~
DrJokepu
It's not really a ternary operator though, because the value that is tested
for existence is only evaluated once, e.g. instead of

    
    
        a != null ? fn(a) : null
    

it's more like

    
    
        (a, fn) => a != null ? fn(a) : null

------
nurettin
Java is already laden with a myriad of utilities such as streams and iterators
which allow you to bypass some of the null checks when dealing with IO or
collections.

If you want to chain calls, you could do that easily by passing "possibly
null" returned values to methods with parameters that are marked @NotNull and
handling null checks as exceptions down the line instead of inventing the
optional type.

------
etaty
Really Hacker news?!

We should use exception all the time. We should adopt exceptional programming.

null is the type to return, just in case we haven't thrown an exception yet.

Everyone know the exception API, how to throw them, how to catch them, we have
to use it all the time. It's great.

We definitely don't need a strong type system, we need an exceptional
programming language, everything is an exception.

------
alexatkeplar
It's quite old now, but this is still one of the best tutorials on dealing
with failure in FP (here, Scalaz):

[https://gist.github.com/oxbowlakes/970717](https://gist.github.com/oxbowlakes/970717)

------
dudul
It's kind of cute to see Java practitioners discover what has been around in
many functional languages for literally decades :)

But, hey, if it helps make Java code cleaner and safer (and Optional and
Either definitely help a lot with that), I'm all for it.

~~~
jfoutz
The oldest version I can think of was Konrad Zeus's architecture for real
computers. Sigma with index variables forcing an expression to zero capture
the same concept. Pretty sure that is an old old idea.

Who did it first can be interesting, but it's rarely much use.

------
beached_whale
This is still possible

java.util.Optional<int> opt = some_null_returning_function( );

~~~
hedora
Hey, don't knock it! Maybe I want a null reference to an Optional<int> to mean
something different than a non-null reference to an optional that contains a
null reference to a boxed Integer.

Database theory tells us that having multiple null values is useful ("eh, I
don't know" vs "your question has no meaning").

Seriously though, it would be great if they just added a "non null reference"
type to the language. C++ has this, and it is useful (even though the compiler
doesn't enforce the non-null bit).

The thing that always irked me about Optional<T> is that they are synonymous
with bare Java references (which also can be null, at least according to the
language spec / compiler). It is like a Java version of

#define THIS_PROGRAM_IS_WRITTEN_IN_C *

to my eyes.

To each their own.

~~~
ebola1717
Eh, in that case you should roll your own container type appropriate to the
business logic, e.g. DBWriteResult<T> (or return Optional<Optional<int>>, but
probably not that)

------
jayajay
> objects don’t magically construct themselves from unstructured data

Very out of context, and very off topic, but this is a profoundly deep
statement... its veracity is questionable and unknown.

------
oconnor663
The Either type there is very similar to Rust's Result type.

~~~
village-idiot
Neither are new concepts, both are probably lifted from Haskell which probably
got it somewhere else prior.

------
Inufu
tl;dr: Don't use Either<L, R> for error handling, it's too general. Use
StatusOr<T>.

More details: [http://www.furidamu.org/blog/2017/01/28/error-handling-
with-...](http://www.furidamu.org/blog/2017/01/28/error-handling-with-
statusor/)

------
hakcermani
In the parse example if the parse fails would we end up with 4 more function
calls ? (rather than one if check and a bail out)

~~~
village-idiot
Any function like map or flatMap that only affects one side of the either is a
no-op if the current value is the wrong side. It's a bit like calling map on
an Optional::empty, a no-op.

------
ndesaulniers
part two will be titled "Shaders and SIMD without If: scattering and
gathering"

------
rco8786
So, Scala.

~~~
Sharlin
Without the huge complexity of Scala and faster compile times. Java 8 is
actually a surprisingly pleasant language to write.

------
arximboldi

      The potential for JSON parsing to fail is encoded in its
      type, not in the potential for a variable to null, or
      false, or for an exception to have been thrown. You’re 
      leaning on the compiler to tell you if you’ve handled the 
      failure cases properly, as the code won’t compile 
      otherwise. Now instead of testing for runtime exceptions 
      you only test to make sure that your business logic is 
      correct.
    

Last time I did Java (it like 7 years ago) the compiler did enforce exceptions
types as part of the signature. Has this changed in between? Otherwise this
does not seem like a valid argument to me.

The OP does discuss checked expcetions a little bit:

    
    
      Checked exceptions guarantee that someone will deal with 
      the issue, but they are extremely annoying, and might 
      result in disparate and different exception handlers all 
      over the place.
    

They don't explain why exceptions are _annoying_ (because the compiler checks
them, just like optional?) and technically there should be exactly the same
number of _try-catch_ handlers as _match_ calls in equivalent Optional<>
code... It seems to me that his arguments are mostly based on aesthetics.
Something the author half-acknowledges by starting their discussion with _"
Well, first off I think it’s beautiful."_

There are some actual problems with exceptions though:

1\. It may be hard to tell from looking at the code which particular calls
inside a function produce which particular kinds of exceptions.

2\. This is particularly problematic with stateful code, as to ensure
exception-save stateful transactions.

3\. They tend to be more expensive for the exceptional codepath -- on the
other hand, they are faster than Optional for the non-exceptional path!

In my experience points 2 and 3 are the most important. Since in Java many
things may throw, one has two think about exception safety anyways most of the
time. This is also a good argument maybe, to just avoid statefulnes instead.

Point 3 is very important. Maybe exceptions should be relegated to truly
exceptional situations, and not be used as a replacement for an _if_.
_Optional <>/Either<>_ is excellent in this sitiation. Still in some languages
do use exceptions for this and familiarity might be something to consider
there (I'm thinking of idiomatic Python signals iteration end or key presence
in maps, but Python also has very different performance characteristics as
Java and does not have static types for the most part anyways...).

As much as I actually love FP, and I am also trying to bring more FP to other
languages (C++), I don't believe in fighting the language for the sake of it.
Having a nuanced conversation about the "why" and a the "when" is important.
Specially when bringing these techniques to communities that are not used to
them and, at the end of the day, already have methods that "Just Work TM".
Otherwise, you end up having reactions like this:
[https://news.ycombinator.com/item?id=13505620](https://news.ycombinator.com/item?id=13505620)

------
general_ai
2 years from now: "we got rid of all the functional bullshit and we're now
using ifs and for loops all over the place; reminds us of our fixie bicycles
and other aspects of our hipster lifestyle".

Over the past two decades I've internalized the value of writing code that's
very easy to understand. Otherwise 6 months later I can't figure things out
myself. This Java style reminds me very much of Scala which seemed like a
decent language until I saw how people actually use it in practice. Noped
right out of that in a hurry.

~~~
eveningcoffee
I think what you are describing is a replacement of widely known constructs
(if, for etc) with an unknown API.

If this API is not understood then the code feels convoluted.

Therefore such projects have to contain a document that will explain the most
common usage of the API to the newcomer. I think that this would remove most
of the confusion.

Naturally it would be good if we had one most widely standardized API that
most of the people are familiar with (like they are with if and for).

~~~
mpweiher
> If this API is not understood then the code feels convoluted.

I think there is more going on here. For the sake of eliminating an if, you
are lifting everything else into what is effectively a separate language with
the original code embedded in that language. Overall, that doesn't look like a
win to me.

After all, what is the actual domain logic? Is it
flatMap().map().flatMap().map()? Or is it
validate().businessLogic().generate() ?

That doesn't mean that what is going in isn't useful, but it seems to me we
need to have a way to specify the lifting without writing it down everywhere,
so that the actual code can be expressed at the base level again.

~~~
Zak
I think replacing common usage patterns of generic control structures with
named constructs adds clarity:

    
    
        for(blah blah blah)
    

tells the reader nothing more than there's going to be a loop, but

    
    
        map(blah blah blah)
    

tells the reader one sequence is going to be transformed into another by
applying a function to each item. That's more informative to the reader if
used for its intended purpose. It has the opposite effect if abused to
repeatedly call a function mainly for its side-effects.

All programming techniques should be viewed as means to write code that's some
combination of readable, reliable and performant, not as ends themselves.

~~~
mannykannot
I don't think the 'map' is actually telling us anything very different than
the 'for'. It is an issue I have with most articles in this vein, and this one
is no different: they give simple, obvious examples that do not suggest that
there is much practical difference between the approaches. We could do with
some articles in which the examples are sufficiently well-developed that the
advantages are clearly non-trivial.

It might be said that any such example would be too complex and drawn-out for
an article like this, but if so, then to me that is like the drunk looking for
his keys under the lamp-post: writing an article that claims a simple example
shows a profound difference, when it does not, suggests that the Emperor's
wardrobe is threadbare. The thing to do is to provide links to articles where
this is worked out in sufficient depth, and I would like to see some of those
links here.

~~~
village-idiot
Sure it does. For can be used for any of the following.

* Mapping (producing a data structure of the same size/type)

* Filtering (producing a data structure of the same type buy smaller)

* Side effects

* Collecting (producing a different data structure)

If you're incredibly unlucky or foolish a for loop might be doing more than
one operation at a time!

Assuming you're only using the return types, map, filter, and collect are very
clear about having only one purpose.

If that's not telling you more I don't know what is.

~~~
mannykannot
That's a fair point about map being constrained in the top-level structure of
what it returns, but what map actually does depends on the function that is
being mapped, and includes the possibility of more than one operation at a
time - and if your language allows side-effects it can do that too. The point
is that you have to look at the 'blah, blah, blah' to know what is happening
(actually, the point of my original post is about extravagant claims of what
simple examples demonstrate...)

~~~
Zak
Sometimes you can use bit-shift operations instead of multiplication and
division, but you probably _shouldn 't_ unless you're an optimizing compiler
or in a situation where you're doing the job of one by hand. Likewise, you
probably shouldn't use a side-effecting function with map.

~~~
mannykannot
Exactly - the mere fact that map is being used does not automatically make
things better; it can be abused just as a loop can (if using a side effect in
a map would be a bad idea, the reasons for it being so would presumably apply
to the equivalent loop.)

A more useful question is how can map, used properly, make things better, and
I stand by my claim that the simple examples of the sort given in this
article, and articles like it, fail to make that case.

~~~
Zak
I think map implies a data transformation without side effects. When I read
'map' in code, that's what I'm expecting. I expect I can replace 'map' with a
parallel version that doesn't guarantee the order in which the elements will
be processed and not change the semantics of the program. I expect that if I
want to swap in an alternate function, I only need to be concerned with its
ability to handle the inputs, produce the outputs and be a logically valid
transformation.

I have no such expectations of a loop until I've read and understood its body.
I hardly have any expectations of a loop at all until I've understood the
whole thing.

And that's how map, used properly makes things better. It communicates what
kinds of things the reader should expect it to do and not do. It reduces the
cognitive load of reading the program, as if, for example, you're scanning for
where a certain side effect happens, you can skip the function called by 'map'
on your first pass through. It shouldn't happen there; maybe it _could_ , but
it should be pretty far down the list of places to check.

~~~
mannykannot
I don't think you are giving yourself enough credit here: it is you who has
chosen to adopt good practices, not map that is obliging you to do so.

Mea culpa: I have to admit that I have sometimes used side-effects in map to
modify the members of a list returned by a function reading data from a
stream.

