ベクトルの内積を求める

Perl 6に関する情報が少なめなのでメモ書きです。

n次元ベクトルa,bの内積
 \bf{a} \cdot \bf{b} = \sum_{i=1}^{n} a_i b_i
を求める関数(演算子)は次のように書けます:

# n次元ベクトルの内積を求める中置演算子
sub infix:<・> (@a, @b) { [+] map {$^a * $^b} zip(@a, @b) };


演算子として定義しているのには特に意味はありません(ただしUTF-8で書かないと多分動きません)。説明は後回しにして,これはこんな風に使えます:

#! /usr/bin/pugs

use v6;

sub infix:<・> (@a, @b) { [+] map {$^a * $^b} zip(@a, @b) };

my Num @x = <2 3 5>;
my Num @y = <1 2 3>;
my Num @z = <1 1 -1>;

(@x・@y).say; # 23
(@x・@z).say; #  0
(@y・@z).say; #  0
(@z・@z).say; #  3

それでは説明です。ます,この関数「・」は中置演算子(infix operator)として定義されています。infix以外にもprefixやらcircumfixやら色々あります。で,(@a, @b) がこの関数(演算子)の取る引数です。これは通常通り。

関数の中は,大きく2つの手順で内積を求めています。最初に map 関数でそれぞれの項の積を求めて(この時点ではリスト),次に [+] という reduction operator*1 を使って和を求めています(この時点でスカラー)。

通常map関数っていえば1つのリストを取って1つのリストを返すもんですが,ここでは2つのリストを取って1つのリストを返しています。ここで便利なのがzip関数。これは一番多いのは for zip(%hash.keys, %hash.values) -> $key, $value { ... } というfor文での使い方でしょう。でも,mapに食わせることもできます。zipによって,それぞれのリストの先頭から,1回のループでそれぞれ1つずつ抜き出してmapに渡してくれます。

それから $^a とか $^b とかいう書き方ですが,これはいわゆる以前の map での $_ や,sort での $a と $b のことです。多分Rubyイテレータに渡すブロックで現れる {|x| ...} と同じ表現,といえば一番分かりやすいのかな,知ってる人には。Perl 6ではハット君 (^) を付けます。

これで map によって返されるリストは ($a[0] * $b[0], $a[1] * $b[1], ...) というものになりました。後はこれを足し合わせて一つの値にして返してやるだけです。「リスト→1つの値」にする便利な高階関数(いわゆるapply関数)というのはPerl 5にはなかったのですが,Perl 6ではreduction operatorというものが使えるようになりました。ここではΣと同じ役割をする [+] 演算子を使っています。これらのreduction operatorはリスト操作の関数なのですが,ちょっと動作が変わっていて,例えば [+] (3 2 1) と書くと,まず始めに「3+2」が評価され,次に「その結果+1」が評価されます。(3+2)+1 の結果なので,スカラー値が返されます。なお,左に結合されるとは限りません。詳しく知りたい方は,雰囲気は 404 Blog Not Found:mapとはおれのことかとcollectいい あたりで,詳細は http://dev.perl.org/perl6/doc/design/syn/S03.html あたりを読んでみてください。

そんなこんなで,内積を求める演算子が定義できます。おさらいすると,infix で中置演算子として宣言し,zip関数によって2つのリストからまとめて値を引き出し,map関数でそれらを掛け算した値を1つのリストにまとめ,それを [+] 演算子で足し算してスカラー値にして返しています。

以上です。

*1:簡約演算子と訳すのか?