Великая Лямбда

Введение

Этот учебный материал показывает как использовать класс Lambda. Ниже приведены два примера - первый пример манипулирует массивом "Array" из чисел используя класс "Lambda". Второй пример манипулирует списком "List" слов импользуя классы "List" и "Lambda". Оба примера выполняют схожие операции с данными.

Лямбда-функции

Класс "Lambda" позволяет нам оперировать на всех "Iterable" объектах сразу. Этот путь обычно предпочтительней чем циклические вычисления поскольку создаёт меньше ошибок и проще для чтения. Для удобства класс "List" содержит несколько часто используемых методов из класса "Lambda".

Класс "Lambda" даёт нам следующие возможности:

  • подсчёт количества элементов ("count")
  • определение пуст ли объект "Iterable" ("empty")
  • определение есть ли элемент в объекте Iterable (has)
  • определения существуют ли элементы удовлетворяющие критериям (exists)
  • поиск индекса заданного элемента (indexOf)
  • определение удовлетворяет ли каждый элемент критериям (foreach)
  • вызов функции для каждого элемента (iter)
  • поиск элементов удовлетворяющих критериям и возврат нового списка 'List (filter'')
  • применение преобразования к каждому элементу и возвращение нового списка List (map)
  • функциональный fold, который так же известен как reduce, accumulate, compress или inject. Для дополнительной информации см. wikipedia (fold)

Итераторы, массивы и списки

Класс "Lambda" работает с "Iterable"-объектами. Тип "Iterable" задаётся через "typedef", таким образом любой класс что имеет метод-"iterator" это по определению "Iterable"-объект. Классы "Array" и "List" являются "Iterable" поскольку оба имеют такой метод. Пользовательские классы могут быть "Iterable" если задать правильный "iterate"-метод внутри класса. Это позволяет существенно увеличить гибкость т.к. можно задействовать Lambda методы.

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


class LambdaNumberTest
{
  public static function main()
  {
    // create and output an array of numbers
    var nums = [1, 3, 5, 6, 7, 8];
    trace("nums: " + nums);

    // count the numbers in the array, see if the array is empty
    trace("count: " + Lambda.count(nums));
    trace("is empty: " + Lambda.empty(nums));

    // check for individual elements in the array
    trace("contains 2: " + Lambda.has(nums, 2));
    trace("contains 3: " + Lambda.has(nums, 3));

    // check if any element fits a criteria
    trace("contains an element less than 10: "
             + Lambda.exists(nums, function(ii) { return ii<10; }));
    trace("contains an element greater than 10: "
             + Lambda.exists(nums, function(ii) { return ii>10; }));

    // check if all elements fit a criteria
    trace("all elements less than 10: "
             + Lambda.foreach(nums, function(ii) { return ii<10; }));
    trace("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; }
    trace("even: " + Lambda.filter(nums, isEven));

    // multiply each element by 2
    var timesTwo = function(num) { return num*2; }
    trace("times two: " + Lambda.map(nums, timesTwo));

    // get the sum of all elements
    var sum = function(num, total) { return total += num; }
    trace("sum: " + Lambda.fold(nums, sum, 0));
  }
}

Compile and run with:

haxe -neko lambda.n -main LambdaNumberTest.hx 
neko lambda.n

The output should be:

LambdaNumberTest.hx:7: nums: [1, 3, 5, 6, 7, 8]
LambdaNumberTest.hx:10: count: 6
LambdaNumberTest.hx:11: is empty: false
LambdaNumberTest.hx:14: contains 2: false
LambdaNumberTest.hx:15: contains 3: true
LambdaNumberTest.hx:18: contains an element less than 10: true
LambdaNumberTest.hx:20: contains an element greater than 10: false
LambdaNumberTest.hx:24: all elements less than 10: true
LambdaNumberTest.hx:26: all elements greater than 10: false
LambdaNumberTest.hx:31: even: {6, 8}
LambdaNumberTest.hx:35: times two: {2, 6, 10, 12, 14, 16}
LambdaNumberTest.hx:39: sum: 30

Word Example


Note that since there is no way to create and populate a list in a single statement we create words as an Array and convert it to a list with the Lambda.list function.
class ListWordTest
{
  public static function main()
  {
    // create a list of words
    var words = Lambda.list(['car', 'boat', 'cat', 'frog']);
    trace("words: " + words);

    // join the words into a string
    trace("join: " + words.join('_'));

    // count the words, see if the list is empty
    trace("count: " + Lambda.count(words));
    trace("is empty: " + words.isEmpty());

    // check for individual elements in the list
    trace("contains cat: " + Lambda.has(words, 'cat'));
    trace("contains tree: " + Lambda.has(words, 'tree'));

    // check if any element fits a criteria
    trace("contains an element less than 5 letters: "
             + Lambda.exists(words, function(ii) { return ii.length<5; })); 
    trace("contains an element greater than 5 letters: "
             + Lambda.exists(words, function(ii) { return ii.length>5; }));

    // check if all elements fit a criteria
    trace("all elements are less than 5 letters: "
             + Lambda.foreach(words, function(ii) { return ii.length<5; }));
    trace("all elements are 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; }
    trace("three letter words: " + words.filter(isThreeLetters));

    // capitalze each word
    var capitalize = function(word) { return word.toUpperCase(); }
    trace("capitalized: " + words.map(capitalize));

    // count the letters in all words
    var countLetters = function(word, total) { return total+=word.length; }
    trace("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:

ListWordTest.hx:7: words: {car, boat, cat, frog}
ListWordTest.hx:10: join: car_boat_cat_frog
ListWordTest.hx:13: count: 4
ListWordTest.hx:14: is empty: false
ListWordTest.hx:17: contains cat: true
ListWordTest.hx:18: contains tree: false
ListWordTest.hx:21: contains an element less than 5 letters: true
ListWordTest.hx:23: contains an element greater than 5 letters: false
ListWordTest.hx:27: all elements are less than 5 letters: true
ListWordTest.hx:29: all elements are greater than 5 letters: false
ListWordTest.hx:34: three letter words: {car, cat}
ListWordTest.hx:38: capitalized: {CAR, BOAT, CAT, FROG}
ListWordTest.hx:42: total letters: 14

Type Inference


We use type inference to improve code readability, but we could have declared each variable's type explicitly. If doing so nums would be defined as:
    var nums : Array<Int> = [1, 3, 5, 6, 7, 8];

words could be defined as:
    var words : Array<String> = Lambda.list(['car', 'boat', 'cat', 'frog']);

isEven could be defined as:
    var isEven : Int->Bool = function(num:Int) 
    { return Math.floor(num/2) == num/2; }

countLetters could be defined as:
    var countLetters : String->Int->Int = function(word:String, total:Int) 
    { return total+=word.length; }

version #12245, modified 2012-01-17 21:16:29 by pinocchio964