Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Consumer1<Tuple[N]<T1, ..., TN>> Tuple.consumer(Consumer[N]<T1, ..., TN>) and Function1<Tuple[N]<T1, ..., TN>> Tuple.function(Function[N]<T1, ..., TN>) #214

Closed
lukens opened this issue Mar 16, 2016 · 14 comments

Comments

@lukens
Copy link

lukens commented Mar 16, 2016

Does anything like the following currently exist in jOOL, or would there be any appetite for adding something like it? I created my own, as I couldn't find anything.

public class Tuple2Lambdas {

    public static <A, B> Predicate<Tuple2<A, B>> predicate(BiPredicate<A, B> predicate) {

        return (tuple) -> predicate.test(tuple.v1, tuple.v2);

    }

    public static <A, B, R> Function<Tuple2<A, B>, R> function(BiFunction<A, B, R> function) {

        return (tuple) -> function.apply(tuple.v1, tuple.v2);

    }

    public static <A, B> Consumer<Tuple2<A, B>> consumer(BiConsumer<A, B> consumer) {

        return (tuple) -> consumer.accept(tuple.v1, tuple.v2);

    }

}

This allows the following:

        Seq.seq(tuple2List)
            .filter(predicate(this::validHit))
            .map(function(this::getDisplayMatch))
            .forEach(consumer(this::processResult));

or:

        Seq.seq(tuple2List)
            .filter(predicate((hit, property) -> hit.getId().equals("blah")))
            .map(function((hit, property) -> tuple(hit.explanation(), property.getId())))

rather than the more long winded:

        Seq.seq(tuple2List)
            .filter(tuple -> validHit(tuple.v1, tuple.v2))
            .map(tuple -> getDisplayMatch(tuple.v1, tuple.v2))
            .forEach(tuple -> processResult(tuple.v1, tuple.v2));

or more opaque:

        Seq.seq(tuple2List)
            .filter((tuple) -> tuple.v2.getId().equals("blah"))
            .map((tuple) -> tuple(tuple.v1.explanation(), tuple.v2.getId())

Most times when I've rolled my own solutions to Java's lambda limitations I then find jOOL has beaten me to it, but I couldn't find anything in this case.

@lukens
Copy link
Author

lukens commented Mar 16, 2016

Something like the following allows it to other TupleNs (though consumers are trickier, and would probably require ConsumerNs to match the FunctionNs [and the predicates are a little hacky]):

public class TupleLambdas {

    public static <A, B> Predicate<Tuple2<A, B>> predicate2(Function2<A, B, Boolean> predicate) {

        return (tuple) -> predicate.apply(tuple.v1, tuple.v2);

    }

    public static <A, B, R> Function<Tuple2<A, B>, R> function2(Function2<A, B, R> function) {

        return (tuple) -> function.apply(tuple.v1, tuple.v2);

    }

    public static <A, B, C> Predicate<Tuple3<A, B, C>> predicate3(Function3<A, B, C, Boolean> predicate) {

        return (tuple) -> predicate.apply(tuple.v1, tuple.v2, tuple.v3);

    }

    public static <A, B, C, R> Function<Tuple3<A, B, C>, R> function3(Function3<A, B, C, R> function) {

        return (tuple) -> function.apply(tuple.v1, tuple.v2, tuple.v3);

    }

...

}

@lukaseder
Copy link
Member

Thank you very much for your suggestion. There are some good ideas here, and there's definitely appetite for it.

There's no need explicitly add the degree to function methods. Overload resolution will be non-ambiguous when the lambda argument types differ in arity. I'm not quite sure what caveat you've spotted with consumers, though.

Note, this could go both ways. There is also potential of adding, e.g.

interface Tuple2<T1, T2> {
    static BiPredicate<T1, T2> biPredicate(Predicate<? super Tuple2<T1, T2>> predicate);
    static <R> BiFunction<T1, T2, R> biFunction(Function<? super Tuple2<T1, T2>, ? extends R> function);
}

The question is, where to put these methods...

@lukens
Copy link
Author

lukens commented Mar 16, 2016

I wasn't sure if it would be necessary to add the degree to the methods, I thought probably not, but erred on the side of caution (as Java always likes to pretend that it's worse at implicit resolutions than it actually is).

The issue with consumers was that there is no consumer above 2nd degree, I tried to get around this by using a Function3 instead, but the problem is then that the lambda you are wrapping has to return something (even if just null), which isn't very nice.

So, you'd have something like:

    public static <A, B, C> Consumer<Tuple3<A, B, C>> consumer(Function3<A, B, C, Void> function) {
        return (tuple) -> function.apply(tuple.v1, tuple.v2, tuple.v3);
    }

But then to use it would need to do something like:

        Seq.of(tuple(1, 'a', "hello"), tuple(1, 'a', "hello")).forEach(consumer((i, c, s) -> {
            System.out.println(i + " - " + c + " - " + s);
            return null;
        }));

If there was a Consumer3 we wouldn't need to do this.

I may be missing a solution to this problem though, or something else in the toolbox that could be used.

@lukaseder
Copy link
Member

I see. We can certainly add those consumers: #215. C# has actions between degrees 1 and 16, too.

@lukaseder
Copy link
Member

Consumer1 .. Consumer16 are now available via #215.

@lukaseder lukaseder changed the title Syntactic sugar helpers for Tuple lambdas Add Consumer1<Tuple[N]<T1, ..., TN>> Tuple.consumer(Consumer[N]<T1, ..., TN>) and Function1<Tuple[N]<T1, ..., TN>> Tuple.function(Function[N]<T1, ..., TN>) Mar 17, 2016
lukaseder added a commit that referenced this issue Mar 17, 2016
…]<T1, ..., TN>) and Function1<Tuple[N]<T1, ..., TN>> Tuple.function(Function[N]<T1, ..., TN>)
@lukaseder
Copy link
Member

I've added your original suggestion for Function and Consumer. Still a bit reluctant about Predicate (and other primitive type specialisations) for now.

My own suggestion will be treated in a separate issue

@lukaseder
Copy link
Member

Thanks again for your suggestion. I think this will be very useful! If you have any other ideas, just shoot, always happy to discuss

@lukens
Copy link
Author

lukens commented Mar 17, 2016

Without predicate() you can't do the .filter() calls in the examples I gave, which I think is a shame. I think it would be fine for the predicate() methods to take a FunctionN<T1, ..., TN, Boolean>, rather than creating PredicateN classes.

I use .filter() fairly extensively, so think it would be nice to have this.

@lukaseder
Copy link
Member

Huh, that would be the missing use-case in #215 (comment)...

There's no problem with adding the predicate() methods as you suggested (the ones taking Predicate and BiPredicate arguments).

However, I don't want to add predicate(FunctionN) methods prematurely. I do have a feeling that in case we do add PredicateN types (as we discover more use-cases), it will have been a mistake to have added predicate(FunctionN) rather than predicate(PredicateN).

I'll review this filter() use-case. Perhaps, after all, I'll just add PredicateN, too.

@lukens
Copy link
Author

lukens commented Mar 17, 2016

From your comments in #215 I had it in my mind that you would be implementing predicate() using FunctionN classes. I agree with your argument that this could potentially be a mistake to do prematurely.

@lukaseder
Copy link
Member

From your comments in #215 I had it in my mind that you would be implementing predicate() using FunctionN classes. I agree with your argument that this could potentially be a mistake to do prematurely.

Yes, that might seem reasonable at first, but it might be unwise to ignore the status quo in the JDK APIs and not support Predicate and BiPredicate. On the other hand, the following overload is not possible:

<T1, T2> Predicate<Tuple2<T1, T2>> predicate(BiPredicate<? super T1, ? super T2> predicate);
<T1, T2> Predicate<Tuple2<T1, T2>> predicate(Function2<? super T1, ? super T2, Boolean> predicate);

While the overload is legal, it makes using lambdas impossible, as there would be ambiguity (unless the lambda is explicitly cast, which is nasty).

Tough choice...

@lukens
Copy link
Author

lukens commented Mar 18, 2016

I think the case is probably building for implementing PredicateN, and then following the same pattern as you did for function(), using the JDK classes where available.

@lukens
Copy link
Author

lukens commented Mar 18, 2016

Another thought on this is some way to make it play nicely with Unchecked.

Personally, I'd be happy for all these methods to automatically wrap in unchecked versions, because I think checked exceptions are Devil spawn, but realise others may disagree.

@lukaseder
Copy link
Member

Another thought on this is some way to make it play nicely with Unchecked.

Indeed, that might be useful at some point. I've created #225 for this. Let's see if it gets any traction first. I feel that this starts exploding exponentially in the number of types that need to be supported...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants