Великая Лямбда
Введение
Этот учебный материал показывает как использовать класс 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; }