8. 関数プログラミング

この章では、関数プログラミングを楽にする機能をいくつか見ていきましょう。

8.1. 関数は第一級市民

関数/サブルーチンは第一級市民です:

  • 引数として渡すことができます。

  • 他の関数から返すことができます。

  • 変数に代入することができます。

これらを示す良い例は map 関数です。
map高階関数 で、他の関数を引数に取ります。

スクリプト
my @array = <1 2 3 4 5>;
sub squared($x) {
  $x ** 2
}
say map(&squared,@array);
出力
(1 4 9 16 25)
解説

まず、引数として与えられる数の2乗を返す squared というサブルーチンを定義しました。
次に高階関数である map に、サブルーチンと配列の2つの引数を渡します。
結果として、配列の要素をすべて2乗したリストが得られます。

サブルーチンを引数として渡す場合には、 & を名前の前につける必要があることに注意してください。

8.2. クロージャ

Perl6ではすべてのコードオブジェクトはクロージャです。つまり、コードオブジェクトは外側のスコープのレキシカル変数を参照できます。

8.3. 無名関数

無名関数lambda(ラムダ) とも呼ばれます。
無名関数は識別子に結び付けられてはいません(つまり名前を持ちません)。

先ほどの map の例を無名関数を使って書き直してみましょう。

my @array = <1 2 3 4 5>;
say map(-> $x {$x ** 2},@array);

サブルーチンを定義して引数として map に渡す代わりに、 map の中で直接定義していることに注意してください。
無名関数 -> $x {$x ** 2} は名前がないので、呼び出すことができません。

Perl6の用語では、この記法は ポインティーブロック と呼ばれます。

ポインティーブロックは、関数を変数に代入するのに使われることもあります:
my $squared = -> $x {
  $x ** 2
}
say $squared(9);

8.4. メソッドチェイン

Perl6では、メソッドはつなげることができます。もう、一つのメソッドの結果を他のメソッドの引数として渡す必要はありません。

今仮に、複数の値の入った配列があって、この配列のユニークな値を大きな方から小さな方にソートしたものを返したいとしましょう。

こんな感じのコードを書いて問題を解決しようとするかもしれません:

my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9 >;
my @final-array = reverse(sort(unique(@array)));
say @final-array;

まず @array に対してunique 関数を呼び、次にその結果を sort に渡し、それからさらにソートの結果を reverse に渡しています。

Perl6では、上の例のようにするのではなく、メソッドをチェインする(つなげる)ことができます。
上の例は、 メソッドチェイン を生かして次のように書くことができます:

my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9 >;
my @final-array = @array.unique.sort.reverse;
say @final-array;

メソッドチェインを使ったほうがずっと 目に優しい ことがお分かりでしょう。

8.5. フィード演算子

フィード演算子 は他の関数プログラミング言語では パイプ とも呼ばれるもので、これを使うとメソッドチェインがさらに見やすくなります。

前向きフィード
my @array = <7 8 9 0 1 2 4 3 5 6>;
@array ==> unique()
       ==> sort()
       ==> reverse()
       ==> my @final-array;
say @final-array;
解説
`@array` に対し、ユニークな要素のリストを返して、
             ソートして、
             リバースして、
             結果を @final-array に入れる

見て分かるように、メソッド呼び出しの流れは上から下の向きです。

後ろ向きフィード
my @array = <7 8 9 0 1 2 4 3 5 6>;
my @final-array-v2 <== reverse()
                   <== sort()
                   <== unique()
                   <== @array;
say @final-array-v2;
解説

後ろ向きフィードは前向きフィードと似ていますが、順番が逆になります。
メソッド呼び出しの流れは下から上の向きです。

8.6. ハイパー演算子

ハイパー演算子hyper operator >>. はリストのすべての要素に対してメソッドを呼び出し、そのすべて結果のリストを返します。

my @array = <0 1 2 3 4 5 6 7 8 9 10>;
sub is-even($var) { $var %% 2 };

say @array>>.is-prime;
say @array>>.&is-even;

ハイパー演算子を使って、 is-prime のようなPerl6で定義済みのメソッドを呼び出すことができます。is-prime を使うと、ある数が素数かどうかを判定できます。
さらに、新しく定義したサブルーチンをハイパー演算子で呼び出すこともできます。その場合は、 &is-even のように、メソッド名の前に & を付ける必要があります。

この非常に役に立つ演算子を使えば、ひとつひとつの値に対して処理を繰り返すのに for ループを書く手間から解放されます。

8.7. ジャンクション

ジャンクション とは、複数の値を論理的な意味で重ね合わせたものです。

下の例で、 1|2|3 はジャンクションです。

my $var = 2;
if $var == 1|2|3 {
  say "The variable is 1 or 2 or 3"
}

一般に、ジャンクションを使うと オートスレッディング が起こります。 ジャンクションのそれぞれの要素に対して演算が行われ、そのすべての結果をひとつにまとめた新しいジャンクションが返されます。

8.8. 遅延リスト

遅延リスト とは、遅延評価されるリストです。
遅延評価では、必要になるときまで式の評価を遅延し、さらにその結果をルックアップテーブルに保存しておくことで何度も評価することを避けます。

次のような利点があります:

  • 不必要な計算を避けることによりパフォーマンスが向上する

  • 無限に大きくなりうるデータ構造も作れる

  • 制御をひとつの流れとして定義できる

遅延リストを作るには、中置演算子 …​ を使います。
遅延リストには、 初期要素ジェネレータ終了値 があります。

単純な遅延リスト
my $lazylist = (1 ... 10);
say $lazylist;

初期要素が 1 で、終了値が 10 です。ジェネレータは定義していないので、デフォルトのジェネレータとして後者関数(+1)が使われます。
つまり、この遅延リストは(必要があれば)これらの要素を返します:1, 2, 3, 4, 5, 6, 7, 8, 9, 10。

無限遅延リスト
my $lazylist = (1 ... Inf);
say $lazylist;

この無限リストは(必要があれば)1から無限大までの整数を返します。言い換えれば、すべての整数を返します。

推論されたジェネレータを使った遅延リスト
my $lazylist = (0,2 ... 10);
say $lazylist;

初期要素が0と2、終了値が10です。 ジェネレータは定義されていません。しかし、Perl6は初期要素からジェネレータを(+2)だと推論します。
この遅延リストは(必要があれば)次の要素を返します:0, 2, 4, 6, 8, 10。

定義されたジェネレータを使った遅延リスト
my $lazylist = (0, { $_ + 3 } ... 12);
say $lazylist;

この例では、ジェネレータが { } で明示的に定義されています。
この遅延リストは(必要があれば)次の要素を返します:0, 3, 6, 9, 12。

明示的なジェネレータを使う場合、終了値はジェネレータが返しうる値の一つでなければなりません。
もし上の例の終了値を12ではなく10にすると、ジェネレータは止まらなくなってしまいます。 ジェネレータは終了値を 飛び越え てしまうからです。

かわりに、 0 …​ 100 …​^ * > 10 に置き換えることができます。
こういう意味になります:0から10より大きい最初の値まで(ただしその値を除く)

この例ではジェネレータは止まりません
my $lazylist = (0, { $_ + 3 } ... 10);
say $lazylist;
この例ではジェネレータは止まります
my $lazylist = (0, { $_ + 3 } ...^ * > 10);
say $lazylist;