Live data from Hacker News

Java Streams and State

blog.frankel.ch

1–10 of 33 posts

Re: Java Streams and State

#3
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.

Re: Java Streams and State

#4

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?

Essentially yes - a Java stream returns an iterable object, which we can perform further operations such as filtering down, mapping to something else or 'collecting' it into another object (et al).

Re: Java Streams and State

#5

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?

Streams are a bit more general. They allow you to use functional operations on sequence of elements (e.g. map, filter, reduce, etc.) to build a processing pipeline which can execute async and in parallel.

``` widgets.stream() .filter(b -> b.getColor() == RED) .mapToInt(b -> b.getWeight()) .sum(); ``` https://docs.oracle.com/javase/8/docs/api/java/util/stream/p...

They aren't specific to Java. In fact they require features such as lambdas that made them less practical until Java 8.

Re: Java Streams and State

#6
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))

Re: Java Streams and State

#7
> 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.

Re: Java Streams and State

#8

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?

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

Re: Java Streams and State

#9

> 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 abov…

I translated your example to Kotlin just for fun, as close as possible. Probably not idiomatic Kotlin, but pretty close, if you ask me.

    typealias FibPair = Pair
    
    val fibSeed = FibPair(0, 1)
    
    fun fibNext(pv: FibPair) = pv.let { (p, v) -> FibPair(v, v + p) }
    
    fun fibList(): Sequence = generateSequence(fibSeed, ::fibNext)
    
    fun main() {
        fibList().take(10).forEach { (p, _) -> print("$p ") }
    }
Honestly I like Kotlin's Sequence abstraction much more than Java streams. It's extremely simple to implement and understand contrary to streams.

Re: Java Streams and State

#10
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)))
Post reply on HN