The Great Lambda
Introduction
This tutorial shows how to use the Lambda class. There are two examples below. The first example manipulates an Array of numbers using the Lambda class. The second example manipulates a List of words using the List and Lambda classes. Both examples perform similar operations on the data.
Lambda Functions
The Lambda class allows us to operate on an entire Iterable at once. This is often preferable to looping routines since it is less error prone and easier to read. For convenience, the List class contains some of the frequently used methods from the Lambda class.
The Lambda class gives us the following capabilities:
- count the number of elements (count)
- determine if the Iterable is empty (empty)
- determine if the specified element is in the Iterable (has)
- determine if any element satisfies a criteria (exists)
- determine if every element satisfies a criteria (foreach)
- find the elements that satisfy a criteria, returning a new List (filter)
- apply a conversion to each element, returning a new List (map)
- functional fold. For more info, see wikipedia (fold)
Arrays, Lists, and Iterables
The Lambda class operates on Iterables. Iterables are typedefs, so any class that has an iterator method is by definition an Iterable. The Array and List classes are both Iterable since they have this method. User defined classes can be Iterables as well by simply defining an appropriate iterator method inside the class. This allows a great deal of flexibility in where Lambda methods can be utilized.
The Lambda.array and Lambda.list functions in the Lambda class convert any Iterable into an Array or List, respectively. Both functions allocate memory for a new data structure and copy each element. The Lambda.map and Lambda.filter functions return Lists even if the input was an Array.
The first example stores the data in an Array and uses Lambda functions directly. The second example stores the data in a List and uses the Lambda class or, where possible, makes calls through the List class's interface.
Count
Lambda.count returns the number of elements in an Iterable. If the Iterable is a Array or List it is faster to use the length property, but the example uses count anyway to show its usage.
Empty
There are several ways to check if an Iterable is empty. For Arrays and Lists, it is best to use the isEmpty method. For all other Iterables it is best to use the Lambda.empty function. It is possible to compare the length (or result of Lambda.count) to zero, but this is slower.
Calling Lambda Functions
Most Lambda functions are called in similar ways. The first argument for all of the Lambda functions is the Iterable on which to operate. Many also take a function as an argument.
It is helpful to look at an example. The exists function is specified as:
static function exists<A>( it : Iterable<A>, f : A -> Bool ) : Bool
The argument
it is the Iterable that Lambda will traverse. The f argument is a function that will be called on each element in it. The definition for exists defines f as taking a single argument with the same type as the it elements, and returning a Bool.
f can be an anonymous function specified in the function call itself (as in the calls to exists in the examples below) or can be a named function (as in the calls to filter in the examples below).
Output
HaXe has built-in routines for converting Arrays to Strings. These routines are automatically called when Arrays are printed using neko.Lib.println. The elements are concatenated together, separated with a comma and space, and surrounded by square brackets. The conversion routine for Lists is similar but it uses curly brackets instead of square brackets.
Number Example
import neko.Lib; class LambdaNumberTest { public static function main() { // create and output an array of numbers var nums = [1, 3, 5, 6, 7, 8]; neko.Lib.println("nums: " + nums); // count the numbers in the array, see if the array is empty neko.Lib.println("count: " + Lambda.count(nums)); neko.Lib.println("is empty: " + Lambda.empty(nums)); // check for individual elements in the array neko.Lib.println("contains 2: " + Lambda.has(nums, 2)); neko.Lib.println("contains 3: " + Lambda.has(nums, 3)); // check if any element fits a criteria neko.Lib.println("contains an element less than 10: " + Lambda.exists(nums, function(ii) { return ii<10; })); neko.Lib.println("contains an element greater than 10: " + Lambda.exists(nums, function(ii) { return ii>10; })); // check if all elements fit a criteria neko.Lib.println("all elements less than 10: " + Lambda.foreach(nums, function(ii) { return ii<10; })); neko.Lib.println("all elements greater than 10: " + Lambda.foreach(nums, function(ii) { return ii>10; })); // find even elements from the array var isEven = function(num) { return Math.floor(num/2) == num/2; } neko.Lib.println("even: " + Lambda.filter(nums, isEven)); // multiply each element by 2 var timesTwo = function(num) { return num*2; } neko.Lib.println("times two: " + Lambda.map(nums, timesTwo)); // get the sum of all elements var sum = function(num, total) { return total += num; } neko.Lib.println("sum: " + Lambda.fold(nums, sum, 0)); } }
Compile and run with:
haxe -neko lambda.n -main LambdaNumberTest.hx neko lambda.n
The output should be:
nums: [1, 3, 5, 6, 7, 8]
count: 6
is empty: false
contains 2: false
contains 3: true
contains an element less than 10: true
contains an element greater than 10: false
all elements less than 10: true
all elements greater than 10: false
even: {6, 8}
times two: {2, 6, 10, 12, 14, 16}
sum: 30
Word Example
import neko.Lib; class ListWordTest { public static function main() { // create a list of words var words = Lambda.list(['car', 'boat', 'cat', 'frog']); neko.Lib.println("words: " + words); // join the words into a string neko.Lib.println("join: " + words.join('_')); // count the words, see if the list is empty neko.Lib.println("count: " + Lambda.count(words)); neko.Lib.println("is empty: " + words.isEmpty()); // check for individual elements in the list neko.Lib.println("contains cat: " + Lambda.has(words, 'cat')); neko.Lib.println("contains tree: " + Lambda.has(words, 'tree')); // check if any element fits a criteria neko.Lib.println("contains an element less than 5 letters: " + Lambda.exists(words, function(ii) { return ii.length<5; })); neko.Lib.println("contains an element greater than 5 letters: " + Lambda.exists(words, function(ii) { return ii.length>5; })); // check if all elements fit a criteria neko.Lib.println("all elements less than 5 letters: " + Lambda.foreach(words, function(ii) { return ii.length<5; })); neko.Lib.println("all elements greater than 5 letters: " + Lambda.foreach(words, function(ii) { return ii.length>5; })); // find three letter elements from the list var isThreeLetters = function(word) { return word.length==3; } neko.Lib.println("three letter words: " + words.filter(isThreeLetters)); // capitalze each word var capitalize = function(word) { return word.toUpperCase(); } neko.Lib.println("capitalized: " + words.map(capitalize)); // count the letters in all words var countLetters = function(word, total) { return total+=word.length; } neko.Lib.println("total letters: " + Lambda.fold(words, countLetters, 0)); } }
Compile and run with:
haxe -neko list.n -main ListWordTest.hx neko list.n
The output should be:
words: {car, boat, cat, frog}
join: car_boat_cat_frog
count: 4
is empty: false
contains cat: true
contains tree: false
contains an element less than 5 letters: true
contains an element greater than 5 letters: false
all elements less than 5 letters: true
all elements greater than 5 letters: false
three letter words: {car, cat}
capitalized: {CAR, BOAT, CAT, FROG}
total letters: 14