Table of Contents
Note: the examples in this document use the JDK 1.5 version of FunctionalJ. For
the JDK 1.4 version, type parameters cannot be used; the generic
Object type must be used instead and results
must be cast to the desired type.
Functions are defined using interfaces, according to the number of parameters accepted by the function. Interfaces are defined for functions that accept 0 to 4 parameters, as well as an arbitrary number of parameters. For the JDK version 1.5 or greater, the types of the result and parameters can also be specified.
Function0<R>Function1<R,P1>Function2<R,P1,P2>Function3<R,P1,P2,P3>Function4<R,P1,P2,P3,P4>FunctionN<R>
public ReturnType method(ParameterType parameter, ...)
Each of the function interfaces contains a call
method that accepts as many parameters as indicated by the name of the interface, and
uses type parameters for each parameter:
public interface Function0<R> { public R call() throws FunctionException; } public interface Function1<R,P> { public R call(P p_param) throws FunctionException; // more methods.... } public interface Function2<R,P1,P2> { public R call(P1 p_param1, P2 p_param2) throws FunctionException; // more methods.... } public interface Function3<R,P1,P2,P3> { public R call(P1 p_param1, P2 p_param2, P3 p_param3) throws FunctionException; // more methods.... } public interface Function4<R,P1,P2,P3,P4> { public R call(P1 p_param1, P2 p_param2, P3 p_param3, P4 p_param4) throws FunctionException; // more methods.... } public interface FunctionN<R> { public R call(Object... p_params) throws FunctionException; public R call() throws FunctionException; // more methods.... }Notice that the declared exception is
FunctionException, which derives from
RuntimeException, an unchecked exception.
Throughout FunctionalJ, this is used to make exception handling optional rather than
strictly required.
All interfaces except Function0 contain other
methods, which are explained in the following sections. To make it easy to define a
function, every interface has a corresponding class, named with the
Impl suffix, that implements every method except for
the call method. Subclassing such a class and
implementing the call method is a convenient way to
define a function. Another way is to use reflection to define a function that refers
to an existing constructor or method.
For functions of 1 or more parameters, there are methods to bind parameters to the function. This means assigning a value to a parameter. This does not call the function; rather, a function of 1 less parameter is returned. Calling the resulting function will use the bound parameter, along with the remaining parameters if applicable. When you bind less parameters than a function requires, this is also known as partial application.
For example, binding a parameter to a function of 3 parameters returns a new function of 2 parameters (without modifying the original function of 3 parameters).
Parameters can be bound at any index. To bind the first parameter, use the
bind method. To bind a parameter at any other
position, use the bindN method, where
N is replaced by the index of the parameter to be
bound. For example, to bind the second parameter, use the
bind2 method.
More than one parameter can be bound with a single method call. In this case, the
method is always called bind and the parameters are
always bound in order starting from the first parameter.
Here is an example that demonstrates parameter binding:
Function3<String,String,String,String> concat3 = new Function3Impl<String,String,String,String>() { public String call(String p_str1, String p_str2, String p_str3) { return p_str1 + p_str2 + p_str3; } }; String result = concat3.call("top", "middle", "bottom"); assertEquals(result, "topmiddlebottom"); Function2<String,String,String> concat2 = concat3.bind2("center"); result = concat2.call("LEFT", "RIGHT"); assertEquals(result, "LEFTcenterRIGHT"); Function1<String,String> concat1 = concat3.bind("first", "second"); result = concat1.call("third"); assertEquals(result, "firstsecondthird"); concat1 = concat2.bind2("right"); result = concat1.call("left"); assertEquals(result, "leftcenterright");
Function composition consists of combining a function of 1
parameter f with a function of any number of
parameters g to obtain a new function of the same
number of parameters h, such that
h(x) = f(g(x)). Therefore the result type of the
function g must be compatible with the parameter
type of the function f. Also, the result type of the
function h will be the same as that of the function
f.
Because the result of calling one function is then used as a parameter to the other
function, function composition is only applicable starting with a function of 1
parameter. Thus only the Function1 interface
contains compose methods. There is a
compose method for each function according to the
number of parameters:
public interface Function1<R,P> { // other methods.... public Function0<R> compose(Function0<P> p_function0); public <P1> Function1<R,P1> compose(Function1<P,P1> p_function1); public <P1,P2> Function2<R,P1,P2> compose(Function2<P,P1,P2> p_function2); public <P1,P2,P3> Function3<R,P1,P2,P3> compose( Function3<P,P1,P2,P3> p_function3); public <P1,P2,P3,P4> Function4<R,P1,P2,P3,P4> compose( Function4<P,P1,P2,P3,P4> p_function4); public FunctionN<R> compose(FunctionN<P> p_functionN); }After using function composition, a new function is produced and can then be used in the same manner as any other function.
Here is an example of using function composition:
Function2<Boolean,Integer,Integer> less = new Function2Impl<Boolean,Integer,Integer>() { public Boolean call(Integer p_first, Integer p_second) { return p_first < p_second; } }; Function1<Boolean,Boolean> not = Operators.not; Function2<Boolean,Integer,Integer> greaterOrEqual = not.compose(less); assertFalse(greaterOrEqual.call(4, 5));
The FunctionN interface defines functions of an
arbitrary number of parameters. When using reflection, this is the interface that is
used to return the produced functions, since the number of parameters is not known at
compile time when using reflection.
In order to use methods that use functions of a specific number of parameters, it
must be possible to transform a FunctionN object
into the interface for that specific number of parameters,
Function1 for instance. To do so, the
f0, f1,
f2, f3 and
f4 methods are available in the
FunctionN interface. Thus the
FunctionN interface also serves as a "bridge"
between functions produced using reflection and functions with a specific number of
parameters.