強大なLambda

導入

 このチュートリアルではLambdaクラスの使い方を解説します。以下に2つの例をあげます。まず、最初の例ではLambdaを使って数値のArrayを操作します。次の例では、単語のListを、ListLambdaのクラスをつかって操作します。どちらの例でも、同じようにデータの操作を行います。

Lambda関数

 Lambdaクラスを使うと、Iterableの要素を一度に操作することができます。この方法をつかうことで、ループを使うよりも読みやすく、エラーを起こしにくく出来る場合があります。利便性のため、ListクラスはLambdaクラスからよく使うメソッドを持っています。

Lambdaクラスにより以下が可能になります。:

  • 要素数を数える(count)
  • Iterableが空か調べる(empty)
  • Iterableが特定の値を待っているか調べる(has)
  • Iterableが特定の条件を満たす値を持っているか調べる(exists)
  • 特定の値の位置を調べる(indexOf)
  • すべての値が条件を満たすか調べる(foreach)
  • すべての要素に対して関数を呼び出す(iter)
  • 特定の条件を満たす値を探し出して、新しいListを返す(filter)
  • すべての値に対して変換を適用して、新しいListを返す(map)
  • 畳みこみ関数。JavaScriptやRubyのinject、C++のaccumulate、PythonやPerlのreduceのに当たる関数。詳しくはwikipedia (fold)

配列(Array)、リスト(List)とIterable

 LambdaクラスはIterableに対して働きます。Iterabletypedefです。そのため、iteratorメソッドを持つ全てのクラスがIterableとして定義されます。配列(Array)とリスト(List)はどちらもiteratorを持っているのでIterableです。ユーザーが定義したクラスも、クラス内でiteratorメソッドさえ定義していればIterableとなります。これの性質によりLambdaの関数はさまざまな場面で柔軟に利用できます。

 Lambda.array関数、Lambda.list関数はあらゆるIterableを配列(Array)、リスト(List)に変換します。どちらの関数も新しいデータ構造用のメモリを確保し、各要素をコピーします。Lambda.mapLambda.filterは入力がArrayだった場合でも、Listを返します。

 最初の例では、Arrayにデータを格納して、Lambdaの関数を直接使っています。次の例では、Listにデータを格納して、Lambdaクラスを使う方法と、Listクラスのインターフェースを通して関数を呼び出す方法を使ってます。

こちらに、Lambdaを使う方法についてさらに詳しく書かれていますUsing Lambda .map() and .filter()

count関数

 Lambda.count関数はIterableの要素数を返します。Iterableが配列(Array)、リスト(List)だった場合、lengthと同じです。下の例では使い方を示すためcountを使用しています。

empty関数

 Iterableが、空かどうか調べる方法はいくつかあります。配列(Array)、リスト(List)の場合、isEmpty関数を使うのがベストです。そのほかの全てのIterableについては'Lambda.emptyを使うのがベストです。lengthLambda.count''を0と比較することも可能ですが遅いです。

Calling Lambda Functions

 ほとんどのLambdaの関数は同じような方法で呼び出します。すべてのLambdaの関数の、最初の引数は操作の対象になるIterableです。そして多くの関数の引数は1つです。

 例を見ましょう。exists関数は以下のように定義されています。

static function exists<A>( it : Iterable<A>, f : A -> Bool ) : Bool

 引数itは、Lambdaが横断するIterableです。引数fは、itのすべての要素に対して呼び出しを行う関数です。existsは、fitの要素と同じ型の引数を一つ持ちBoolを返す関数として定義しています。

 fは匿名関数(下の例のexistsで使われているような関数)で構いませんし、名前を持った関数(下の例のfilterで使われているような関数)でも構いません。

アウトプット

 Haxeは、ArrayStringに変換する組み込みのルーティンをもっています。これらのルーティンはArrayneko.Lib.printlnで出力した際に自動的に呼び出されます。要素はカンマとスペースで区切って連結されて[]で囲まれます。Listでも同じように変換されますが[]の代わりに{}が使用されます。

数値の例


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));
  }
}

以下でコンパイルと実行:

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

期待される出力:

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

単語の例


 ひとつのステートメントでリストを生成することはできません。以下の例では、Arrayを生成した後、Lambda.listListに変換しています。
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));
  }
}

コンパイルと実行:

haxe -neko list.n -main ListWordTest.hx 
neko list.n

期待される出力:

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

型推論


 コードを読みやすくするために型推論を用いることができますが、明示的に型指定を行うこともできます。明示的に型指定する場合、numsは以下のように定義されます。
    var nums : Array<Int> = [1, 3, 5, 6, 7, 8];

wordsは以下のように定義できます。
    var words :List<String> = Lambda.list(['car', 'boat', 'cat', 'frog']);

isEvenは以下のように定義できます。
    var isEven : Int->Bool = function(num:Int) 
    { return Math.floor(num/2) == num/2; }

countLettersは以下のように定義できます。
    var countLetters : String->Int->Int = function(word:String, total:Int) 
    { return total+=word.length; }
version #15991, modified 2013-02-20 12:15:41 by shohei909