Not a bug, just proposing new utilities that help dealing with Tuple2 (could fit into reactor.util.function.Tuples for example):
public static <Left, Right, NewLeft> Function<Tuple2<Left, Right>, Tuple2<NewLeft, Right>> left(Function<Left, NewLeft> mapper) {
return tuple -> /* Tuples. */of(mapper.apply(tuple.getT1()), tuple.getT2());
}
public static <Left, Right, NewRight> Function<Tuple2<Left, Right>, Tuple2<Left, NewRight>> right(Function<Right, NewRight> mapper) {
return tuple -> /* Tuples. */.of(tuple.getT1(), mapper.apply(tuple.getT2()));
}
public static <Left, Right, Return> Function<Tuple2<Left, Right>, Return> both(BiFunction<Left, Right, Return> mapper) {
return tuple -> mapper.apply(tuple.getT1(), tuple.getT2());
}
public static <Left, Right> Tuple2<Right, Left> reverse(Tuple2<Left, Right> tuple) {
return /* Tuples. */.of(tuple.getT2(), tuple.getT1());
}
Quite handy when used with zipped Monos, for example:
import reactor.util.function.Tuples;
import static reactor.util.function.Tuples.both;
import static reactor.util.function.Tuples.left;
import static reactor.util.function.Tuples.right;
import static reactor.core.publisher.Mono.just;
public Mono<String> example() {
return just(40)
.zipWith(just("hell"))
.map(left(i -> i + 2))
.map(right(s -> s + "o"))
.map(Tuples::reverse)
.map(both((s, i) -> s + " " + i + " :)")); // now wraps "hello 42 :)"
}
If you're interested, let me know, I would be happy to create a PR ;)
The reactor-extra project is the better place for this to go. I'd (selfishly) suggest the existing TupleUtils class for a prospective PR. I think you'll find the equivalent of both already exists there.
As a point of suggestion (again if you choose to submit a PR), I'd expand reverse to cover Tuple2 to Tuple8. I also wonder if, instead of left()/right(), is there something to be done with first()/second()/.../eighth()? Since the Tuple# classes extend one another, a single implementation could service each of them? Maybe?
A derivative question there would be " should the default Tuple2" utils be ported back to Tuples since they don't involve new contracts and would pair nicely with zipWith.
@nebhale Oops, thanks, I wasn't aware of this reactor-extra, looks like I forgot to RTFM... I'll have a look at this TupleUtils class, maybe it already covers my needs.
@smaldini Good point! Let me know what you prefer.
I think we can start off in reactor-extra and we'll review promoting candidates utils for 3.2
@smaldini Sorry, haven't fully understood... Would a PR be welcome? If yes, in which repo?
i meant open a PR with @nebhale suggestions in reactor-extra, and I'll leave this issue open here as a possible candidate for 3.2, separately.
@nebhale, I've implemented reverse for all tuples.
Concerning having first, second, ..., eighth for all tuples:
Test:
@Test
public void first() {
assertThat(
TupleUtils.first(first -> {
assertThat(first).isEqualTo(1);
return -1;
}).apply(Tuples.of(1, 2, 3, 4, 5, 6, 7, 8))
).isEqualTo(Tuples.of(-1, 2, 3, 4, 5, 6, 7, 8));
}
First try:
public static <T1, T2, N> Function<Tuple2<T1, T2>, Tuple2<N, T2>> first(Function<T1, N> first) {
return tuple -> Tuples.of(first.apply(tuple.getT1()), tuple.getT2());
}
Fails:
org.junit.ComparisonFailure:
Expected :[-1,2,3,4,5,6,7,8]
Actual :[-1,2]
Indeed, I'm actually creating a Tuple2.
Second try:
@SuppressWarnings("unchecked")
public static <T1, T2, N> Function<Tuple2<T1, T2>, Tuple2<N, T2>> first(Function<T1, N> first) {
return tuple -> {
Object[] array = tuple.toArray();
array[0] = first.apply((T1) array[0]);
return Tuples.fromArray(array);
};
}
Succeeds, but uses @SuppressWarnings("unchecked"), so you may have policies against this. Also, the returned type Function<Tuple2<T1, T2>, Tuple2<N, T2>> should probably be something like Function<V1, v2>, where V1 extends Tuple2<T1, T2> and V2 extends Tuple2<N, T2>, but still not constraining enough as V1 can be Tuple3 and V2 Tuple4...
I'm afraid first2, first3, ..., first8 is needed actually.
@sp00m I agree with your final paragraph and suspect that the following is sufficient:
@SuppressWarnings("unchecked")
<TUPLE extends Tuple2<FIRST, SECOND>, TUPLE_PRIME extends Tuple2<FIRST_PRIME, SECOND>, FIRST, FIRST_PRIME, SECOND> Function<TUPLE, TUPLE_PRIME> first(Function<FIRST, FIRST_PRIME> f) {
return tuple -> {
Object[] elements = tuple.toArray();
elements[0] = f.apply((FIRST) elements[0]);
return (TUPLE_PRIME) Tuples.fromArray(elements);
};
}
Specifically, I think you have to lean on extends to ensure that the bigger tuple types make it out.
Nope 馃槮
@Test
public void anotherFirst() {
Mono
.just(Tuples.of(1, 2, 3, 4, 5, 6, 7, 8))
.map(first(Math::negateExact))
.map(first(Math::negateExact))
.block();
}
doesn't compile. You'd have to strongly type the value on the way out, because the compiler just gives up with Object.
@nebhale Indeed, but because only the return value would change (plus the mapper's parameters types, but erased), overloading isn't possible, which means we need different function names (if I'm not wrong)...
We could imagine first2 for Tuple2, first3 for Tuple3, etc., but I'm afraid this could make people think "do something with the first three elements on the given tuple" while it actually is "do something with the first element of the given Tuple3". Plus this will create a load of functions actually: first2 to first8, second2 to second8, third3 to third8, fourth4 to fourth8, ..., seventh7 to seventh8, and eighth8.
Well, not sure what to do here. I think I'll stick with my local left and right for now, unless you find a better solution?
Is it still worth it I push the reverse functions you think?
Good question. I've never needed to reverse anything so I don't have a strong opinion. Might be nice to have the PR and get it into reactor-addons so that if a Tuple2 promotion happens, reverse() might be useful in that context.
That first type of method could make sense on Tuple2..Tuple8 directly. I was thinking more like a Tuple2<R, T2> mapT1(Function<T1, R> mapper) signature. Would that + the methods available in reactor-extra (staying there) be interesting enough?
Most helpful comment
The
reactor-extraproject is the better place for this to go. I'd (selfishly) suggest the existingTupleUtilsclass for a prospective PR. I think you'll find the equivalent ofbothalready exists there.As a point of suggestion (again if you choose to submit a PR), I'd expand
reverseto coverTuple2toTuple8. I also wonder if, instead ofleft()/right(), is there something to be done withfirst()/second()/.../eighth()? Since theTuple#classes extend one another, a single implementation could service each of them? Maybe?