
Java Streams and State - pplonski86
https://blog.frankel.ch/java-streams-state/
======
dkarl
_This is only marginally better, as the computation logic is still "hidden" in
the lambda. _

This is a very weird statement. It's hidden in exactly the place where it's
needed! Where is it hidden _from?_ Where else would you need it? If you're
reading the code, it's right there, you don't have to click to definition like
you do with Pair::next.

What should I get from reading Pair::next? There's no natural "next" for pairs
of numbers. How would I guess that this guy's idea of "next" involves
incrementing the first number and then squaring it? To make this equally
readable you'd have to rename "Pair" or "next" so that the names were just as
informative and easy to read as the lambda itself. So you end up with an
unwieldy-but-meaningful name like SquareNumbersIterationState or
SquaresSequence.State, which is the classic hallmark of OO programming run
amok (aka, the Kingdom of Nouns.)

It's so much nicer to have the lambda inline and see exactly what's happening.
One of my favorite things about functional style is that it doesn't force you
to noun and name things that are easy to describe but hard to name. Sometimes
something can just stand for itself instead of needing an awkward name. I
mean, Java isn't the best at this, but this isn't hard to read at all:

    
    
      pair -> new Pair(pair.index + 1, Math.pow(pair.index + 1, 2)))

~~~
blankaccount
I really like your summary of the situation. I'd add that the author seems to
have tried to fix a functional readability problem with OO techniques, ending
up with the same kingdom of nouns problem - the worst of both worlds.

I think the functional solution would be to separate increment and transform:
Stream.iterate(0, i -> i + 1).map(i -> new Pair(i, Math.Pow(i, 2));

------
RagingCactus
For the Fibonacci example the author claims:

> Notice how state was introduced? It made the code easier to read.

Correct me if I'm wrong, but the only state in that snippet lives in
Stream.iterate(), the Fibonacci object is still immutable and next() on it is
a pure function. To me this still looks very much like a functional
programming approach.

Which just shows that moving business logic into properly named abstractions
is good, no matter the programming paradigm.

The real stateful example is the IncrementSupplier and the impure get()
method. I personally don't really like it ( Stream.iterate(0, i -> i + 1) is
pretty readable to me), but that's probably just personal taste.

------
herbstein
> Notice how state was introduced? It made the code easier to read.

That quote shows that the author kinda misses the point here.

    
    
        type FibPair = (Int, Int)
    
        fibSeed :: FibPair
        fibSeed = (0, 1)
    
        fibNext :: FibPair -> FibPair
        fibNext (p, v) = (v, v + p)
    
        fibList :: [FibPair]
        fibList = iterate fibNext fibSeed
    

The above code does exactly the same as his Fibonacci example, and it's
written in pure Haskell. I'd argue the above is way more readable.

I get the following output:

    
    
        0 1 1 2 3 5 8 13 21 34
    

With the following main function:

    
    
        main :: IO ()
        main = putStrLn . unwords . map (show . fst) . take 10 $ fibList
    

Which just takes the first element of each generated tuple, maps it to the
string representation, and then adds a space between each number.

~~~
antonvs
Right, his Fibonacci class is completely immutable, i.e. it's purely
functional.

He's confusing different usages of the word "state" \- exhortations to avoid
state are almost always about _mutable_ state, but he doesn't appear to
recognize that distinction.

~~~
jarym
Tell that to the person at work who recently came back from a FP workshop and
has declared war on all state...

Developers to tend to go from extremes.

~~~
antonvs
The question is what he means by "all state".

If we take the OP post as definitional, then eliminating state means
eliminating compound data structures, because that's the usage of "state"
implied by that Fibonacci example.

Hopefully the person at your work has something less extreme in mind. Ask him
to explain what he means by "state".

------
Sharlin
The biggest problem with the 1^2...n^2 example is syntactic, honestly. If Java
had native tuples and destructuring, the code would look quite clean:

    
    
        Stream.iterate((1,1), ((idx, _)) -> (idx+1, Math.pow(idx+1, 2)));
    
    
    

As an aside, as each term of this particular sequence only depends on the
index, not the previous term, the cleanest way would be to just map an index
sequence:

    
    
        // Full overflow prevention left as an exercise to the reader
        IntStream.rangeClosed(0, Integer.MAX_VALUE).map(n -> Math.pow(n, 2))

------
dmolony
It's a shame the author never actually ran the code, it has bugs.

For example, the Factorial next() function should be:

    
    
      return new Factorial (index + 1, value * (index + 1));
    

And of course the iterate line should be:

    
    
      Stream.iterate (Factorial.SEED, Factorial::next)

------
bastawhiz
As someone who hasn't written Java since college, I am not familiar with
streams. Is this similar in principle to a Python/JS generator function?

~~~
agumonkey
I wonder how people who stopped suff.. writing java before java 8 feel about
lambda expressions and streams.

~~~
MarkMc
I've been writing Java for 20 years. Streams are a definite improvement, but
half the time I find myself rewriting a streams approach to an old-fashioned
loop to make the code more readable.

~~~
firepoet
When you say "readable," do you mean to OO programmers? Functional
programmers? All programmers?

For example, as a 5-year Clojure convert who wrote Java for 15 years before
that, old-fashioned loops are less readable now, as they hide the essentials
of what is really happening. For example, you might be mapping a collection
from one type of value to another, or reducing it to some other form, etc. But
both use loops. You have to scan all over the place in the loop construct to
see what it's really doing. I'd much rather see the function that is happening
than the procedure by which the function is accomplished.

~~~
MarkMc
Readable to me, and I assume most other Java programmers. Here's an example I
recently came across [0]:

Option A:

    
    
      static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
        Function<Y, Z> function) {
          return input.keySet().stream()
              .collect(Collectors.toMap(Function.identity(),
                                        key -> function.apply(input.get(key))));
      }
    

Option B:

    
    
      static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
        Function<Y, Z> function) {
          Map<X, Z> result = new HashMap<>();
          input.forEach((k, v) -> result.put(k, function.apply(v)));
          return result;
      }
    
    

Personally I find Option B more readable. Another example:

Option A:

    
    
      protected boolean isCashBasis(CalendarDate effectiveDate) {
        return priorVatReturns.stream()
          .filter(r -> r.period.contains(effectiveDate))
          .findFirst()
          .map(r -> r.basis == AccountingBasis.CASH_BASIS)
          .orElseGet(() -> super.isCashBasis(effectiveDate));
      }
    

Option B:

    
    
      protected boolean isCashBasis(CalendarDate effectiveDate) {
        for (VatReturn r : priorVatReturns) {
          if (r.period.contains(effectiveDate)) {
            return r.basis == AccountingBasis.CASH_BASIS;
          }
        }
        return super.isCashBasis(effectiveDate);
      }
    

Again I would choose Option B, although I suppose Option A isn't too bad.

[0]
[https://stackoverflow.com/a/25905196/76295](https://stackoverflow.com/a/25905196/76295)

~~~
agumonkey
everytime I use generic statically typed oop I can't stop thinking about
clojure or ml languages.. The amount of noise is deafening.

------
the8472
I don't think the stateful approach with .generate() is thread-safe if you
make the stream parallel. The API doc says it's an unordered stream,
parallelizing it would mean concurrent reads/writes in the supplier.

Stream.iterate() docs on the other hand guarantee happens-before ordering for
invocations.

------
exabrial
Small want item about Java Streams... I wish this was a language feature.
Allow type declarations in lambdas:

currently: collection.stream()....bunch of stuff....forEach(item ->
something(item))....

this would be nice: collection.stream()....bunch of stuff....forEach(String
item -> something(item))....

When you get 50 levels deep on a really long lambda, this would make debugging
the things far easier.

~~~
mc13
It is definitely possible to specify the type of the parameter(s) in a lambda
expression in Java.

