Thursday 11 July 2013

Using TotallyLazy the functional library for Java (et al.)

In software development when I revisit an imperative language, after coding in a functional language, there are many idioms that I miss. Such as pattern matching, language integrated sequence manipulation, tuples and, of course, functions-as-data... the list goes on. Languages such as Java can mimic some of these “out of the box” functional features. For example, functions-as-data can be mimicked by wrapping functions in an anonymous class, although you would still lose function composition.

Creating tuple classes is quite easy, here's an example: Tuples in Java. Pattern matching is more complicated, an implementation can be found here: Pattern Matching in Java. For “sequence manipulation” there are a number of libraries out there. One of which is TotallyLazy, which I will now document. Upon first using the library I immediately hit a wall: there are so many features that I didn't know where to begin. At that time I couldn't find any helpful guides on the Internet, so I decided to write this blog.


To begin with, you ‘import’ classes and methods from the TotallyLazy library. Somewhat like this:


import com.googlecode.totallylazy.Sequence;
import static com.googlecode.totallylazy.Sequences.sequence;

The ‘import static’ directive takes a ‘public static’ method from the Sequences class and puts it into the global namespace of the current code file. It means you can write lines of code like this:


   sequence(1, 2, 3, 4);

That will create a sequence of integers from 1 to 4. In order to confirm that the code works you could use a testing framework. If you’re unfamiliar with testing frameworks then you can find a simple example here: Coding the Simplest Testing Framework in Java


For simplicity’s sake I will be using the testing framework specified in the previous link. Here's an example of it’s use:

   test
(7+8).is(15);
   test
(sequence(1, 2, 3, 4)).is(1, 2, 3, 4);

Here the ‘test’ method takes a value and returns an object that has an ‘is’ method. You pass in the expected result into the ‘is’ method and, if successful, it will continue silently. If the test fails then an exception is thrown.

Below is the full listing of a class called FunctionalPlay, which I created when learning about the TotallyLazy library. I wrote comments in-line to explain what is happening. When the ‘Go()’ method is called it will either return “Success!” or a message from the failed test.
(Note: in it’s current state it will be successful.)

package net.intrepidis.functionalapp;

import com.googlecode.totallylazy.Callable2;
import com.googlecode.totallylazy.Characters;
import com.googlecode.totallylazy.Function1;
import com.googlecode.totallylazy.Function2;
import com.googlecode.totallylazy.Maps;
import com.googlecode.totallylazy.ReducerFunction;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.numbers.Numbers;
import java.util.List;
import java.util.Map;
import static com.googlecode.totallylazy.Callables.toString;
import static com.googlecode.totallylazy.Characters.characters;
import static com.googlecode.totallylazy.Option.none;
import static com.googlecode.totallylazy.Option.some;
import static com.googlecode.totallylazy.Sequences.iterate;
import static com.googlecode.totallylazy.Sequences.repeat;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.numbers.Numbers.*;
import static com.googlecode.totallylazy.predicates.InPredicate.in;
import static net.intrepidis.library.SimplestTest.test;
import static net.intrepidis.library.functional.CharacterJoiner.characterJoiner;
import static net.intrepidis.library.functional.CharacterJoiner.newStr;
import static net.intrepidis.library.functional.FunctionalFactory.*;
import static net.intrepidis.library.functional.PatternMatch._;
import static net.intrepidis.library.functional.PatternMatch.match;
import static net.intrepidis.library.functional.PatternMatchMany.matchMany;


public class FunctionalPlay {
   
public static String Go() {
       
try {
           totallyLazyTests
();
           
return "Success!";
       
} catch (Exception e) {
           
return e.getMessage();
       
}
   
}

   
private static void totallyLazyTests() throws Exception {
       
// A simple test to demonstrate the use of the testing framework.
       test
(7 + 8).is(15);

       
// Here we test that (1,2,3,4) is indeed returned.
       test
(sequence(1, 2, 3, 4)).is(1, 2, 3, 4);

       
// The same can be achieved with the 'range' function.
       test
(range(1, 4)).is(1, 2, 3, 4);

       
// The sequence can be assigned to a variable.
       Sequence
<?> seq1to4 = range(1, 4);
       
// ... and then tested.
       test
(seq1to4).is(1, 2, 3, 4);

       
// This returns (1,2,3,4,1,2,3,4,1,2,3,4,...) forever.
       Sequence
<?> seq1to4forever = seq1to4.cycle();
       
//... It doesn't actually contain an infinite sequence of items (that would obviously exhaust the system's memory). That's the point of TotallyLazy, things aren't evaluated until they actually need to be. That's lazy evaluation.
       
// However, how could we test that sequence? We'll just take the first 6 of the infinite sequence, that should be sufficient.
       test
(seq1to4forever.take(6)).is(1, 2, 3, 4, 1, 2);

       
// This returns (1,2,3,...) to infinity.
       Sequence
<?> positiveIntegers = iterate(increment, 1);
       
//... Note that 'increment' is a statically defined object in the TotallyLazy libraries. Here's the test:
       test
(positiveIntegers.take(4)).is(1, 2, 3, 4);

       
// An infinite sequence of "car"s.
       Sequence
<?> cars = repeat("car");
       test
(cars.take(2)).is("car", "car");

       
// The filter function takes a "callable" object which has the method 'call'. Here the 'even' object is used, which is defined in the TotallyLazy library. You can create your own callables to do anything you please.
       test
(sequence(1, 2, 3, 4, 5).filter(even)).is(2, 4);

       
//===========================================

       
// It's not possible to pass an int[] into the 'sequence' function (due to a Java generics constraint). However, there exists another function called 'numbers' which we can use.
       Sequence
<? extends Number> seqFromArray = numbers(new int[]{2, 5});
       test
(seqFromArray).is(2, 5);

       
// You *can* pass an Integer[] into 'sequence'.
       
Integer[] arr = new Integer[]{6, 4};
       test
(sequence(5, 7).join(sequence(arr))).is(5, 7, 6, 4);

       
// Convert a sequence into a standard Java list.
       
List<? extends Number> listFromSequence = seqFromArray.toList();

       
//===========================================

       
// Messing around with sequences of characters.
       Sequence
<Character> alphaLow = Characters.range('a', 'z');
       Sequence
<Character> alphaHigh = alphaLow.map(toUpperCase);
       Sequence
<Character> alphabet = alphaLow.join(alphaHigh);
       Sequence
<Character> vowels = characters("aeiouAEIOU");
       Sequence
<Character> consonants = alphabet.filter(in(vowels).not());

       
// Here we get all the consonants from a sentence.
       Sequence
<Character> chars = characters("This is my sentence.");
       test
(
           chars
// a sequence of characters
           
.filter(in(consonants)) // filter to only consonant characters
           
.fold(newStr(), characterJoiner) // glue characters into a new string
       
).is("Thssmysntnc");

       
//===========================================

       test
(sequence(1, 2).map(toString)).is("1", "2");
       test
(sequence(1, 2, 3).take(2)).is(1, 2);
       test
(sequence(1, 2, 3).drop(2)).is(3);
       test
(sequence(1, 2, 3).tail()).is(2, 3);
       test
(sequence(1, 2, 3).head()).is(1);
       test
(sequence(1, 2, 3).reduce(sum)).is(6);

       
//===========================================

       
// Because of how the built-in 'divide' function works, this sequence of Integers wont divide to what I initially thought:
       test
(sequence(3, 8).map(divide(2))).isnt(1, 4); // it doesn't divide to an integer result

       
// The numbers divide to integer fractions. (i.e. not '1.5' but '3/2')
       test
(sequence(3, 8).map(divide(2))).is(Numbers.divide(3, 2), 4); // returns (3/2,4)

       
// In order to do an integer divide I created my own function 'div'. (The code for it is in the 'FunctionalFactory' class, below.)
       test
(sequence(3, 8).map(div(2))).is(1, 4);

       
//===========================================
       
// Some more tests using my own functions.
       test
(sequence("hi", "how").map(strLen)).is(2, 3);
       test
(sequence(3, 8).map(mulAdd(2, 1))).is(7, 17);
       
//===========================================

       
// Here the 'find' function returns an 'Option' object. If nothing is found then it returns an object equivalent to 'none()'. If it is found then an object equivalent to 'some()' is returned. The actual value found can be retrieved with the 'Option.value()' method.
       test
(sequence(1, 2, 5).find(even)).is(some(2));
       test
(sequence(1, 3, 5).find(even)).is(none(Integer.class));

       
//===========================================

       test
(sequence(1, 2, 3).contains(2)).is(true);
       test
(sequence(1, 2, 3).exists(even)).is(true);
       test
(sequence(1, 2, 3).forAll(odd)).is(false);
       test
(sequence(1, 2, 3).foldLeft(0, add)).is(6);
       test
(sequence(1, 2, 3).toString()).is("1,2,3");
       test
(sequence(1, 2, 3).toString(":")).is("1:2:3");

       
//===========================================
       primes
(); // every prime number
       fibonacci
(); // the fibonacci sequence
       powersOf
(3); // powers of 3 (i.e (1,3,9,27,...)
       
//===========================================

       
// Iterates an infinite sequence, starting from 1, filters to only the even numbers, takes the first 10 (2,4,6,8...16,18,20), then finds the average.
       test
(iterate(increment, 1).filter(even).take(10).reduce(average)).is(11);

       
// Use 'cons' to prepend a head element.
       test
(sequence(2, 3).cons(7)).is(7, 2, 3);
   
}
}

Some of the static functions and objects above have come from the following classes below. The first class is FunctionalFactory which contains general tools for use with TotallyLazy.

package net.intrepidis.library.functional;


import com.googlecode.totallylazy.Arrays;
import com.googlecode.totallylazy.Callable1;
import com.googlecode.totallylazy.Sequence;
import com.googlecode.totallylazy.Sequences;
import java.util.List;
import java.util.ArrayList;
import java.util.AbstractList;
import static com.googlecode.totallylazy.Sequences.sequence;
import com.googlecode.totallylazy.numbers.Integers;
import com.googlecode.totallylazy.Option;


public class FunctionalFactory {
   
public static Callable1<Integer, Integer> mulAdd(final int mul, final int add) {
return new Callable1<Integer, Integer>() {
public Integer call(Integer operand) {
  return operand * mul + add;
}
};
}

   
public static Callable1<Integer, Integer> div(final int divisor) {
return new Callable1<Integer, Integer>() {
  public Integer call(Integer operand) {
      return operand / divisor;
    }
  };
}
public static final Callable1<String, Integer> strLen = new Callable1<String, Integer>() {
  public Integer call(String from) {
    return from.length();
  }
};
public static final Callable1<Character, Character> toUpperCase = new Callable1<Character, Character>() {
  public Character call(Character c) throws Exception {
    return Character.toUpperCase(c);
  }
};
}

The next class is CharacterJoiner, which is used to glue a sequence of characters together as a string. The FunctionalPlay class above shows how to use it.

package net.intrepidis.library.functional;


import com.googlecode.totallylazy.ReducerCombinerFunction;
import com.googlecode.totallylazy.ReducerFunction;


public class CharacterJoiner extends ReducerCombinerFunction<Character, CharSequence> {
public static final CharacterJoiner characterJoiner = new CharacterJoiner();
public static CharSequence newStr() {
  return new StringBuilder();
}
public CharSequence call(CharSequence a, Character b) throws Exception {
  ((StringBuilder)a).append(b);
  return a;
}
@Override public CharSequence identity() {
  return newStr();
}
@Override public CharSequence combine(CharSequence a, CharSequence b) throws Exception {
 
StringBuilder s = new StringBuilder();
  s.append(a);
  s.append(b);
  return s;
}
}

EDIT:
I have recently required use of the 'unique' method. As it's not immediately obvious how to use it I thought I'd update this blog. 

public static void testUnique() throws Exception {
 class DefinedType {
  int i1;
  int i2;
  public DefinedType(int i1, int i2) {
   this.i1 = i1;
   this.i2 = i2;
  }
  @Override
  public String toString() {
   return String.format("%d,%d", i1, i2);
  }
 }
 DefinedType[] ints = new DefinedType[] {
  new DefinedType(1,2),
  new DefinedType(6,5),
  new DefinedType(1,2), // Duplicate value, but different object.
  new DefinedType(4,9) };
 // This test will demonstrate that the 'unique' function
 // will remove the duplicate value.
 test(sequence(ints)
  .unique(new Callable1<DefinedType,String>() {
   @Override
   public String call(DefinedType dt) throws Exception {
    return dt.toString();
   }}))
  .is(ints[0], ints[1], ints[3]);
}

The return value of the 'call(...)' method should be a type that overrides the 'hashCode()' method of Object. In this example I am making use of the String class' hash coding. 

EDIT 2:

It's worth noting that the Sequence class is best used only within the context of a method and not passed out of that method or into other methods. This is because of the lazy evaluation of the sequences, therefore you may run into trouble when using multi-threading or resource handling. It would be best to cause the sequence to be evaluated, for example by converting to a list like this: sequence(1,2,3).toList();

-------------------------------

This code is completely free to use by anybody. It is held under the Do What You Want To Public License: http://tinyurl.com/DWYWTPL

No comments:

Post a Comment