APL 入門
次の式は「APL 知的形式の最高峰」なる誇大広告的タイトルの過去記事に現れたものだ。約束どおり、今回はこの式の解説を時間を掛けてしようと思う。
今回のタイトルを「APL 入門」とした。入門と言ってもブログの記事なのだから、とことんやるというわけにもいかない。そこで、APL についてまったく知らない人が、この式にたどり着き理解することをだけを目標にしようと思う。それならば、目標が明確だし、入門の範囲を限定できる。
まずは上の式に与えられた意味を確認したいと思う。
ある正の整数 R >1 が与えられたとき、その整数までに含まれるすべての素数の列を求めるという問題だった。APL に R の値と上の式を与えて ENATER キーを押せば、素数のリストが表示される。これが上の式の意味だ。
前もって上の式について言っておくなら、素数の定義ともいえるもので式のその内容は難しいものではない。極めて簡明なことで実際見たとおり極めて短い式である。また、R はとくに意味はなく A でも B でも P でも Q でもよい。
改めてこの式を眺めてみると、わかってしまえば実に簡単。同じ問題を Haskell で書いて比べてみるといい。効率を考えたロジックが必要なのだって? 本来はこの式を示した通りに計算する必要はなく、やりたいことを APL に示したのだから、ロジック自体の最適化を含めた実行を APL 側で引き受けてもよいはずだ。
それがコードから独立した未来の言語 APL に期待するものだ。もちろん、NARS2000 はそんな面倒は見てくれないだろう。 しかし、それは実装の問題であり APL 言語の問題ではないはずだ。(NARS2000 - http://www.nars2000.org/)
さて、では APL に入門してみよう。まずは簡単な四則演算から。
2+3×4
14
3×4+5
27
(3×4)+5
17
APL は演算子に優先順位はなくすべてが同じ優先順位だ。右から左に結合していく。必要ならばカッコ () を使って結合順序をコントロールできる。
-(¯2ׯ3)
-6
上のように APL では演算子の ¯(マイナス)と演算子の - (マイナス)を明確に区別する。
次は ⍳(イオタ)演算子だ。
⍳10
1 2 3 4 5 6 7 8 9 10
1 から 10 までのリストが表示された。リストは APL ではベクトル(ドイツ読み。ベクターでもよい)という。一般的に ιN で 1 から N までの連続した整数のベクトルを生成する。なお、システム変数の設定により開始数の 1 を 0 にすることもできる。
課題の式には出てこないが、次は ρ(ロウ)演算子を見てみよう。
ρ1 2 3 4 5 6 7 8 9 10
10
ρ 演算子はベクトルの要素数を返す。APL では、一般的に右辺には任意次元数の Array がくる。また、左辺に次のようなベクトルを与えると、右辺のベクトルを流し込むように 2行 3列のマトリックスを作る。なお、ρ の左側のベクトルのことを次元ベクトルという。一般的に M を Array とすると ρM は M の次元ベクトルであり、ρρM でえられる数を階数(ランク)という。
2 3ρ1 2 3 4 5 6
1 2 3
4 5 6
2 3ρ⍳6
1 2 3
4 5 6
V←ρM←2 3ρ⍳6 ⍝ V は M の次元ベクトル
2 3
S←ρρM←2 3ρ⍳6 ⍝ S は M の階数(ランク)
2
V← は右辺の式の V への代入である。また、上で ⍝ はコメントを示す。
3↑1 2 3 4 5 6 7 8 9 10
1 2 3
¯3↑ι10
8 9 10
3↓1 2 3 4 5 6 7 8 9 10
4 5 6 7 8 9 10
¯3↓ι10
1 2 3 4 5 6 7
↑(取り)と ↓(落とし)は、左辺がスカラーの拡張としてベクトルの場合もあるが今回は割愛する。小さな仕様で理解してしまえばことが簡単だ。
S←'MY NAME IS'
R←'MAZIN'
⎕←Q←S,' ',R,'.'
MY NAME IS MAZIN.
A←1 2 3 4 5 6 7 8 9 10
B←0 1 1 0 1 0 1 0 0 0
⎕←B/A
2 3 5 7
まず最初の例では、文字列 S と R を連結して Q に代入している。⎕←Q の部分はプリント装置への代入と見ることができるだろう。なお、変数は1文字である必要はなく、MYVARIABLE なども可能であり、更に NARS2000 では MyVariable なども可能だ。
上の / にはいくつかの機能があるが、ここでは「圧縮」と呼ばれる機能に絞っている。B は論理ベクトルで、その値の 1 か 0 かで右辺のベクトルの要素を選別している。
A←2 3 4
B←10 20 30
⎕←C←A+.×B
200
上の例の +.× は「内積」と呼ばれるもので、右辺と左辺のベクトルの要素同士を掛け算した結果の要素をすべて加算する。したがってその結果はスカラーとなる。
上の例では、C←(10×2)+(20×3)+(30×4) で C は 200 となる。これが内積だ。
A←2 3 4
B←10 20 30
⎕←A∘.×B
20 40 60
30 60 90
40 80 120
上の例の ∘.× は「外積」と呼ばれるもので、ここでは厳密な定義はおこなわないが、例のような演算をおこなう。例では各要素同士のすべての積を要素に持つマトリックスを作り出している。
3∊1 2 3 4 5
1
6∊1 2 3 4 5
0
3 4 6∊1 2 3 4 5
1 1 0
∼1
0
∼0
1
∼1 0 1 0 1 0 1 0
0 1 0 1 0 1 0 1
APL の論理演算子のうち ∊(メンバー)と ∼(否定)の例を挙げた。ベクトルまでの範囲なので単純であり見ただけでわかるだろう。
さあ、これで準備ができただろう。そこで例の式をここに再掲載しよう。
例として R に 10 を与えた場合で考えてみよう。
⍳10
1 2 3 4 5 6 7 8 9 10
⍳10 は 1 から 10 までの自然数列のベクトルだった。
R←1↓ι10
R
2 3 4 5 6 7 8 9 10
1↓ι10 で 1 をもとのベクトルから削除している。これは 1 は素数と定義しないため、それを除外しているわけだ。これで元の式は、
(∼R∊R∘.×R)/2 3 4 5 6 7 8 9 10
となる。
⎕←M←R∘.×R
4 6 8 10 12 14 16 18 20
6 9 12 15 18 21 24 27 30
8 12 16 20 24 28 32 36 40
10 15 20 25 30 35 40 45 50
12 18 24 30 36 42 48 54 60
14 21 28 35 42 49 56 63 70
16 24 32 40 48 56 64 72 80
18 27 36 45 54 63 72 81 90
外積によって R 同士のすべての積の組み合わせを求めることができた。そのマトリックスを M とした。式は以下のようになった。
(∼R∊M)/2 3 4 5 6 7 8 9 10
つまり、
(∼2 3 4 5 6 7 8 9 10∊M)/2 3 4 5 6 7 8 9 10
最初に与えられたベクトルの要素(1 を除外している)が、2つの数を掛けた結果と一致するならば、それは素数ではない。
(∼0 0 1 0 1 0 1 1 1)/2 3 4 5 6 7 8 9 10
論理否定(∼)を反映させると次のようになる。
1 1 0 1 0 1 0 0 0/2 3 4 5 6 7 8 9 10
2 3 5 7
これを計算した結果が答だが、これはどういう意味か。素数でない論理ベクトルを否定したのだから、結果は素数の論理ベクトルができたはずで、それを与えられた数までの自然数列(1 は除外されている)に適用すれば、素数のベクトルができているはずである。
結果の 2 3 5 7 は確かに 10 までのなかにある素数のリストである。
もし最初に 100 を与えれば、(100-1)×(100-1) だけの外積を計算するわけだ。これが 10000 であったら (10000-1)×(10000-1) だから 1億個ほどの計算をすることになる。
APL はパソコンの性能が飛躍的に進歩し人間側の都合に合わせた論理だけを与える効率の悪さが許容されるようになり、普通には使えそうもない特殊な記号のすべてががユニコードに マッピングされることで、無理なく利用できる環境が整ってきたといえるだろう。
APL こそは究極の高級言語といえるだろう。
また、APL はエクセルの次元を上げスーパーエクセルにしたようなものとも言え、業務に使っても効率を上げることができるものであるはずだ。
これを機会に更に勉強してみようという人が一人でも増えれば幸いです。