Skip to content

Instantly share code, notes, and snippets.

@msfroh
Created December 9, 2011 15:17
Show Gist options
  • Save msfroh/1451926 to your computer and use it in GitHub Desktop.
Save msfroh/1451926 to your computer and use it in GitHub Desktop.
Functional Programming in Java Part 1
public abstract class Function2<R, T1, T2> {
/* ... Previous declaration of apply ... */
public final Function1<Function1<R, T2>, T1> curry() {
return new Function1<Function1<R, T2>, T1>() { // The Function1 that we're returning
public Function1<R, T2> apply(final T1 i1) { // Capture first parameter
return new Function1<R, T2>() { // The function of n-1 parameters
@Override
public R apply(final T2 i2) { // Take second parameter
// Return result from original function
return Function2.this.apply(i1, i2);
}
};
}
};
}
}
/**
* Base class for functions of one parameter.
* <T1> The type of the parameter
* <R> The return type of the function
*/
public abstract class Function1<R, T1> {
public abstract R apply(final T1 i1);
}
// A unary function that takes an Integer as a parameter and returns an Integer
final Function1<Integer, Integer> timesTwo = new Function1<Integer, Integer>() {
@Override
public Integer apply(Integer i1) {
return i1 * 2;
}
};
// The following statement outputs:
// timesTwo.apply(5) = 10
System.out.println("timesTwo.apply(5) = " + timesTwo.apply(5));
/**
* The following method takes a List of elements of type T1 and a
* function that turns T1s into Rs, and returns the List of Rs
* produced by applying the function to each element of the input list.
*
* Suppose inputList is [t1_1, t1_2, t_3, ...] and f(t1_i) = r_i.
* Then transformList(inputList, f) = [r_1, r_2, r_3, ...].
*/
public static <R, T1> List<R> transformList(final List<T1> inputList,
final Function1<R, T1> f) {
List<R> outputList = new ArrayList<R>();
for (T1 t : inputList) {
outputList.add(f.apply(t));
}
return outputList;
}
// The output of the next statement is:
// transformList([1,2,3,4,5], timesTwo) = [2, 4, 6, 8, 10]
System.out.println("transformList([1,2,3,4,5], timesTwo) = "
+ transformList(Arrays.asList(1, 2, 3, 4, 5), timesTwo));
/**
* Base class for functions of two parameters.
* <T1> The type of the first parameter
* <T2> The type of the second parameter
* <R> The return type of the function
*/
public abstract class Function2<R, T1, T2> {
public abstract R apply(final T1 i1, final T2 i2);
}
/**
* The following function takes a String (str) and an Integer (count),
* and returns a String.
* Specifically, it returns the String arising from "count" repetitions
* of "str".
*/
final Function2<String, String, Integer> repeatString =
new Function2<String, String, Integer>() {
@Override
public String apply(String str, Integer count) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < count; i++) {
builder.append(str);
}
return builder.toString();
}
};
// The output of the next statement is:
// repeatString.apply("badger", 3) = badgerbadgerbadger
System.out.println("repeatString.apply(\"badger\", 3) = "
+ repeatString.apply("badger", 3));
/**
* The following function takes an Integer (val) and an Integer -> Integer
* function (func), and returns the Integer "val + func(val)".
*/
final Function2<Integer, Integer, Function1<Integer, Integer>>
applyFunctionAndAddResultToOriginalValue =
new Function2<Integer, Integer, Function1<Integer, Integer>>() {
@Override
public Integer apply(Integer val, Function1<Integer, Integer> func) {
return val + func.apply(val);
}
};
// The output of the next statement is:
// applyFunctionAndAddResultToOriginalValue.apply(5, timesTwo) = 15
System.out.println("applyFunctionAndAddResultToOriginalValue.apply(5, timesTwo) = "
+ applyFunctionAndAddResultToOriginalValue.apply(5, timesTwo));
// DOES NOT COMPILE
// Type parameters R and T1 are not bound to anything.
final Function2<List<R>, List<T1>, Function1<R, T1>> transformList =
new Function2<List<R>, List<T1>, Function1<R, T1>>() {
@Override
public List<R> apply(List<T1> i1, Function1<R, T1> i2) {
List<R> outputList = new ArrayList<R>();
for (T1 t : inputList) {
outputList.add(f.apply(t));
}
return outputList;
}
};
/**
* This method returns a version of transformList based on the
* input type T1 and return type R of the Function1 that we will
* pass to it.
*/
private static <R, T1> Function2<List<R>, List<T1>, Function1<R, T1>>
buildTransformList(Class<R> returnClass,
Class<T1> inputClass) {
return new Function2<List<R>, List<T1>, Function1<R, T1>>() {
@Override
public List<R> apply(List<T1> i1, Function1<R, T1> i2) {
List<R> outputList = new ArrayList<R>();
for (T1 t : i1) {
outputList.add(i2.apply(t));
}
return outputList;
}
};
}
/**
* Here is a type-safe instance of transformList that transforms a List<Integer>
* into a different List<Integer>, using any Function1<Integer, Integer>.
*/
Function2<List<Integer>, List<Integer>, Function1<Integer, Integer>>
transformIntListToIntList =
buildTransformList(Integer.class, Integer.class);
// The next statement outputs:
// transformIntListToIntList.apply([1, 2, 3], timesTwo) = [2, 4, 6]
System.out.println("transformIntListToIntList.apply([1, 2, 3], timesTwo) = "
+ transformIntListToIntList.apply(Arrays.asList(1, 2, 3), timesTwo));
// The following commented statement would not compile.
// We are giving it a Function1<Integer, String>, when it requires a
// Function1<Integer, Integer>.
/*
System.out.println("transformIntListToIntList.apply([1, 2, 3], stringLength) = "
+ transformIntListToIntList.apply(Arrays.asList(1, 2, 3), stringLength));
*/
/**
* The following instance does no type-checking of the list
* types or the input/return types of the passed function.
*/
final Function2<List, List, Function1> rawTransformList =
new Function2<List, List, Function1>() {
@Override
public List apply(List i1, Function1 i2) {
List outputList = new ArrayList();
for (Object t : i1) {
outputList.add(i2.apply(t));
}
return outputList;
}
};
// The next statement outputs:
// rawTransformList.apply([1,2,3], timesTwo) = [2, 4, 6]
System.out.println("rawTransformList.apply([1,2,3], timesTwo) = "
+ rawTransformList.apply(Arrays.asList(1, 2, 3), timesTwo));
/**
* Here is our previous stringLength example, but this time as a Function1.
*/
final Function1<Integer, String> stringLength =
new Function1<Integer, String>() {
@Override
public Integer apply(String i1) {
return i1.length();
}
};
// The following compiles without warning, but at runtime throws:
// java.lang.ClassCastException:
// java.lang.Integer cannot be cast to java.lang.String
System.out.println("rawTransformList.apply([1,2,3], stringLength) = "
+ rawTransformList.apply(Arrays.asList(1, 2, 3), stringLength));
/**
* The following instance does no type-checking of the list
* types or the input/return types of the passed function.
*/
private static final Function2 rawTransformList =
new Function2<List, List, Function1>() {
@Override
public List apply(List i1, Function1 i2) {
List outputList = new ArrayList();
for (Object t : i1) {
outputList.add(i2.apply(t));
}
return outputList;
}
};
/**
* This method returns the above instance as a "type-safe" version.
* The compiler will warn us that it cannot check the type-safety,
* but callers of this factory method won't see any warnings. We
* are localizing our "unsafe" behaviour to this method.
*
* For another example of this "trick" see e.g Collections.emptySet()
* in the Java standard library.
*/
public static <R, T1> Function2<List<R>, List<T1>, Function1<R, T1>>
buildTransformList(Class<R> returnClass,
Class<T1> inputClass) {
return rawTransformList;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment