user2751441
user2751441

Reputation: 177

Java 8 Functional Programming - Passing function along with its argument

I have a question on Java 8 Functional Programming. I am trying to achieve something using functional programming, and need some guidance on how to do it.

My requirement is to wrap every method execution inside timer function which times the method execution. Here's the example of timer function and 2 functions I need to time.

timerMethod(String timerName, Function func){
  timer.start(timerName)
  func.apply()
  timer.stop()
}

functionA(String arg1, String arg2)

functionB(int arg1, intArg2, String ...arg3)

I am trying to pass functionA & functionB to timerMethod, but functionA & functionB expects different number & type of arguments for execution.

Any ideas how can I achieve it.

Thanks !!

Upvotes: 3

Views: 1523

Answers (3)

fps
fps

Reputation: 34460

Introduction

The other answers show how to use a closure to capture the arguments of your function, no matter its number. This is a nice approach and it's very useful, if you know the arguments in advance, so that they can be captured.

Here I'd like to show two other approaches that don't require you to know the arguments in advance...

If you think it in an abstract way, there are no such things as functions with multiple arguments. Functions either receive one set of values (aka a tuple), or they receive one single argument and return another function that receives another single argument, which in turn returns another one-argument function that returns... etc, with the last function of the sequence returning an actual result (aka currying).

Methods in Java might have multiple arguments, though. So the challenge is to build functions that always receive one single argument (either by means of tuples or currying), but that actually invoke methods that receive multiple arguments.


Approach #1: Tuples

So the first approach is to use a Tuple helper class and have your function receive one tuple, either a Tuple2 or Tuple3:

So, the functionA of your example might receive one single Tuple2<String, String> as an argument:

Function<Tuple2<String, String>, SomeReturnType> functionA = tuple -> 
    functionA(tuple.getFirst(), tuple.getSecond());

And you could invoke it as follows:

SomeReturnType resultA = functionA.apply(Tuple2.of("a", "b"));

Now, in order to decorate the functionA with your timerMethod method, you'd need to do a few modifications:

static <T, R> Function<T, R> timerMethod(
        String timerName, 
        Function<? super T, ? extends R> func){
    return t -> {
        timer.start(timerName);
        R result = func.apply(t);
        timer.stop();
        return result;
    };
}

Please note that you should use a try/finally block to make your code more robust, as shown in holi-java's answer.

Here's how you might use your timerMethod method for functionA:

Function<Tuple2<String, String>, SomeReturnType> timedFunctionA = timerMethod(
    "timerA", 
    tuple -> functionA(tuple.getFirst(), tuple.getSecond());

And you can invoke timedFunctionA as any other function, passing it the arguments now, at invocation time:

SomeReturnType resultA = timedFunctionA.apply(Tuple2.of("a", "b"));

You can take a similar approach with the functionB of your example, except that you'd need to use a Tuple3<Integer, Integer, String[]> for the argument (taking care of the varargs arguments).

The downside of this approach is that you need to create many Tuple classes, i.e. Tuple2, Tuple3, Tuple4, etc, because Java lacks built-in support for tuples.


Approach #2: Currying

The other approach is to use a technique called currying, i.e. functions that accept one single argument and return another function that accepts another single argument, etc, with the last function of the sequence returning the actual result.

Here's how to create a currified function for your 2-argument method functionA:

Function<String, Function<String, SomeReturnType>> currifiedFunctionA =
    arg1 -> arg2 -> functionA(arg1, arg2);

Invoke it as follows:

SomeReturnType result = currifiedFunctionA.apply("a").apply("b");

If you want to decorate currifiedFunctionA with the timerMethod method defined above, you can do as follows:

Function<String, Function<String, SomeReturnType>> timedCurrifiedFunctionA =
    arg1 -> timerMethod("timerCurryA", arg2 -> functionA(arg1, arg2));

Then, invoke timedCurrifiedFunctionA exactly as you'd do with any currified function:

SomeReturnType result = timedCurrifiedFunctionA.apply("a").apply("b");

Please note that you only need to decorate the last function of the sequence, i.e. the one that makes the actual call to the method, which is what we want to measure.

For the method functionB of your example, you can take a similar approach, except that the type of the currified function would now be:

Function<Integer, Function<Integer, Function<String[], SomeResultType>>>

which is quite cumbersome, to say the least. So this is the downside of currified functions in Java: the syntax to express their type. On the other hand, currified functions are very handy to work with and allow you to apply several functional programming techniques without needing to write helper classes.

Upvotes: 1

Carcigenicate
Carcigenicate

Reputation: 45741

Don't hold onto the arguments and then pass them at the last moment. Pass them immediately, but delay calling the function by wrapping it with another function:

Producer<?> f1 =
    () -> functionA(arg1, arg2);

Producer<?> f2 =
    () -> functionB(arg1, arg2, arg3);

Here, I'm wrapping each function call in a lambda (() ->...) that takes 0 arguments. Then, just call them later with no arguments:

f1() 
f2()

This forms a closure over the arguments that you supplied in the lambda, which allows you to use the variables later, even though normally they would have been GC'd for going out of scope.

Note, I have a ? as the type of the Producer since I don't know what type your functions return. Change the ? to the return type of each function.

Upvotes: 1

holi-java
holi-java

Reputation: 30686

you should separate it into two things by Separation of Concerns to make your code easy to use and maintaining. one is timing, another is invoking, for example:

//                                       v--- invoking occurs in request-time
R1 result1 = timerMethod("functionA", () -> functionA("foo", "bar"));
R2 result2 = timerMethod("functionB", () -> functionB(1, 2, "foo", "bar"));


// the timerMethod only calculate the timing-cost
<T> T timerMethod(String timerName, Supplier<T> func) {
    timer.start(timerName);
    try {
        return func.get();
    } finally {
        timer.stop();
    }
}

IF you want to return a functional interface rather than the result of that method, you can done it as below:

Supplier<R1> timingFunctionA =timerMethod("A", ()-> functionA("foo", "bar"));
Supplier<R2> timingFunctionB =timerMethod("B", ()-> functionB(1, 2, "foo", "bar"));


<T> Supplier<T> timerMethod(String timerName, Supplier<T> func) {
    //      v--- calculate the timing-cost when the wrapper function is invoked
    return () -> {
        timer.start(timerName);
        try {
            return func.get();
        } finally {
            timer.stop();
        }
    };
}

Notes

IF the return type of all of your functions is void, you can replacing Supplier with Runnable and then make the timerMethod's return type to void & remove return keyword from timerMethod.

IF some of your functions will be throws a checked exception, you can replacing Supplier with Callable & invoke Callable#call instead.

Upvotes: 4

Related Questions