#Code Review Request: Java Tuples

1 messages · Page 1 of 1 (latest)

safe matrix
#

Background:
A Tuple utility for Java.

Tuple2<String, Integer> output = getNameAndAge();

public Tuple2<String, Integer> getNameAndAge() {
  return tup.of("Luigi", 420);
}

This allows for multiple values to be returned from methods without having to create single-use records every time.

It also allows the coder to express obvious associations. So instead of passing 8 params into a function where the first 4 correspond to 1 logical entity and the second 4 correspond to the other one, we can pass in two Tuple4's, so that their association is made explicit.

Code:
https://github.com/raagnair/jbelt/blob/main/src/main/java/com/raagnair/belt/TupleTools.java

robust pondBOT
#

<@&987246883653156906> please have a look, thanks.

hollow patio
#

Move the tuple tools stuff into the tuple class it goes with

#

having it all in one place is a bit confusing

round cairn
#

And well

#

most of your utils are useless
only useful ones are I guess the LambdaTools

#

Like what's the point of ArrayTools and CollectionTools when you have streams

safe matrix
#

Thanks for the feedback @hollow patio @round cairn

@ Oro -- So you mean keep the static .of methods in the TupleTools class, but move Tuple2 and Tuple3 (etc) to classes of their own?

safe matrix
#

Though this thread was only for TupleTools, I was thinking of making separate threads (later) for the other tools

round cairn
#

And remember that jit exist

safe matrix
safe matrix
#

Though I should post the benchmarks somewhere, and maybe even dump some code so that others can run benchmarks on their own

round cairn
#

in what context ?

safe matrix
#

2-5x as fast, and after warmup, and the contexts were:

  • mapping primitive arrays from one primitive to some object
  • mapping object arrays to other object arrays
  • mapping object arrays to primitive arrays
round cairn
safe matrix
#

I do see CollectionUtils.fold becoming mostly redundant after Stream Gatherers are released, though I suspect CollectionUtils will still be faster

round cairn
safe matrix
#

Also:

I am pretty sure that I can make a stream which is as fast or really close to a regular loop

This library is basically shorthand for that lol

round cairn
# safe matrix What do you mean by context?

So let's said that what you said is true, you gain 5x speed
Then let's say that you have an app, which has a lifespan of 10H, among those 10 hours, the stream approach took 50ms. And so your approach would have taken 10ms.
So you managed to gain 40ms in the span of 10H... this isn't great

round cairn
round cairn
safe matrix
# round cairn So let's said that what you said is true, you gain 5x speed Then let's say that ...

Ah, to be clear, when I say "benchmark" I mean comparing speeds of the ArrayUtils.map function vs. the equivalent Stream.map.toArray() functions. If some service runs for 50 years and uses this only 1 time, then yes, it would not have a significant impact. When I said "5x faster" I certainly didn't mean the entire Java program does everything 5x as fast -- just that the map operation runs much faster.

round cairn
#

So your lib is at best extremely niche

#

So niche that it is probably just better to recode it

safe matrix
#

Maybe. I generally disagree with your idea of what benchmarking is for.

#

But I appreciate the feedback nonetheless

round cairn
#

Also note that streams have access to optimisations not possible in loops

round cairn
#

I didn't talk about benchmarking here

safe matrix
#

I assumed "it" was the speed increase

round cairn
#

for the reason explained before

#

and well

#

even then, it depends of the benchmark

#

I could find a case where a stream would perform better

safe matrix
#

I can see it depending on use-case. Anyone using loops in a hot path would certainly find a 5x increase significant.

safe matrix
round cairn
#

sure, but then, you could make it even more significant by dropping the lambda, etc

#

and you would go down a rabit hole

safe matrix
#

I actually tried comparing this version vs. raw loops with no lambdas and there wasn't a significant cost. But I'll have to check if maybe the JIT wasn't warmed up enough.

round cairn
safe matrix
#

I would be keen to know if there are examples where map operations are somehow faster in Streams than in raw loops.

round cairn
#

But the question is how close a stream can be to a loop

safe matrix
#

Agreed. For me a 5x speedup is well worth the usage. I've seen even faster speedups on not-warmed-up VMs but that's neither here nor there, as that would mostly only help people doing adhoc scripts, etc.

round cairn
robust pondBOT
round cairn
#
    public <A extends Dest, B extends Dest, Dest>
    Dest[] asArray(Tuple2<A, B> input, Dest[] dest) {
        dest[0] = input.a();
        dest[1] = input.b();
        return dest;
    }

Also, why would you return the array ?

safe matrix
#

When you want a neat "output" to the operation

round cairn
#

Also isn't it unsafe to trust a given array like this ?

safe matrix
safe matrix
warped notch
# safe matrix Avoids intermediate allocations

If you're worried about allocations, then the Tuple is a wrong fit for wrapping primitives. The record will be cheaper (and as strong-type more likely to be spotted scalarisation or at least monomorphic dispatch.

safe matrix
#

Though I agree that using Tuples for primitives would come at the additional cost of boxing.
I have considered providing a whole litany of primitive Tuples for all permutations of primitives. From Tuple2 to Tuple5.

round cairn
warped notch
#

We use method local records for containers for intermediate processing in the same method and nested records for returning or passing data-containers around. Where our codebases used to use tuple-types we've almost completely eliminated them and produced more self-documenting code in the process.

#

One thing we use is a ProjectedList that provides a projected view over another list (in cases where the consumer can't take a Stream). This is trading a little per-element read cost for a near zero creation cost - if you're only reading once, it way cheaper than any copy-behaviour.

safe matrix
# warped notch We use method local records for containers for intermediate processing in the sa...

Yeah I can see the merit of using bespoke records instead of tuples.
This is to provide the shorthand for those that want tuples.

I've seen tuples used in production a lot where there's a lean towards pure functions. Often times there are tons and tons of functions with different return types, that will take the form of: Tuple2<Output, State> someFn(State: in) where it not only produces an Output but it also conveys the modified State after the computation.

safe matrix
warped notch
#

No lib, it's not a complex implementation - the List implementation used by Arrays.asList(T...) is a good guide.

  • Most functionality is delegated to the enclosed list
  • additions are rejected
  • retrievals are mapped

From memory there was a little effort to ensure efficient iteration and sublist behaviour.

This yielded some big performance changes in places where the List is only partially consumed. But note, we only use it when we have a List, have to provide a List, and know that it's likely to only be consumed once or less.

safe matrix
warped notch
#

The assumption is that we're only consuming nominally once, so memoisation would be a net loss. For other use-cases we'd lean on Stream unless profiling showed a significant hotspot worthy of special treatment.

safe matrix
warped notch
#

Surely the decision is at the time of creation. In which case two types is probably simpler.