J言語の使用によるASCII文字の地獄への手引き

J言語ってどんな言語?

  • Jのサイトによれば「モダンで、高級で、汎用的で、ハイパフォーマンスな」言語らしい。
  • 有限オートマトンを2バイトで実装できるらしい。
  • 超幾何級数を2バイトで計算できるらしい。
  • 関数のメモ化が2バイトで実装できるらしい。

実際のコードを見てみよう。たとえばクイックソートの実装はこんなかんじ。

   quicksort=: (($:@(<#[) , (=#[) , $:@(>#[)) ({~ ?@#)) ^: (1<#)

ただの難読言語?いいえ、もっとひどい難読言語です。難読だけど、機能だけみれば実用的。

処理系のインストール

http://www.jsoftware.com/stable.htmからお使いのプラットフォームに合ったインストーラ/シェルスクリプト/ディスクイメージをダウンロードしよう。

J言語の基本

品詞

まずは次のプログラムを見てほしい。

#include<stdio.h>

int main()
{
    puts("Hello, world!");
    return 0;
}

これはC言語によるHello, world!プログラムだ。この中でmainやputsは「関数」、"Hello, world!"や0は「リテラル」と呼ばれる。

一般にプログラミング言語では関数や変数、定数などを組み合わせてプログラムを作る。J言語でもこれは同じなんだけど、呼び方がちょっと違う。

J言語には品詞って概念がある。それで一般的に「何かの処理をするもの」つまり「関数」は「動詞」と言ったりする。自然言語みたいだ。

他にも定数を名詞、単語同士をつなげる働きをもつものを接続詞、動詞に作用するものを副詞って言う。

アレイ(array)

Jにはアレイという概念がある。arrayといっても単なる配列のことじゃない。

一般にいうスカラー、ベクトル、行列とかいうものをさらに拡張、一般化したのがアレイ。J言語の名詞の基礎だ。

アレイにはランク(=次元)というのがあって、それぞれ次のように対応している。

ランク Jでの呼称 一般的な呼称
0 アトム スカラー
1 リスト ベクトル
2 テーブル 行列

このアレイに対してまとめて処理を行えるっていうのもJの大きな特徴だ。

演算子の合成

J言語最大にして最強、そして最難読の特徴、それが演算子の合成。

数学でも関数の合成というのはある。関数f, gを合成して g(f(x)) を表すとかね。でもJの合成規則はもっと複雑。

Jの合成規則はまとめて「トレイン」と呼ばれていて、基本である「フック」と「フォーク」の2つを理解すればいい。合成の詳細に関してはまた後で。

基本単語

Jにはもとから入っている単語がとても豊富だ。いろいろな機能が揃っている。中にはいつ使うのかわからないようなものまである(というかそういう機能も多い)。

これを全て理解するのは本当に至難の技。これを書いてるJAPLJですら全部を理解してるわけじゃない。Jの基本単語一覧を見ながら、というスタイルが多いね。

とりあえず始めよう

Jには対話的な実行環境が入っている。これからJのソースをいくらか書くことになるけど、こういう感じなので覚えておこう。

   program
result

つまりスペースでインデントされている部分には実際のJ言語のプログラムを書く。それで、その結果対話環境から返ってくる値はインデントなしで書く。これは対話環境をそのままコピーしたものになる。

まずは動詞の使い方

動詞には2種類ある。というより、ひとつの動詞が2つのはたらきを持つ。ひとつは単項、もうひとつは二項。

簡単にいえば単項っていうのは引数がひとつ、二項っていうのは引数がふたつって意味。

たとえば * は動詞だけど、次のように2通りの使い方がある。

   3 * 5    NB. 二項なら掛け算
15
   *3    NB. 単項なら符号
1

二項の場合は演算子をはさむように引数を、単項の場合は演算子の右に引数を書く。

あ、ちなみに "NB." っていうのはコメントの印。その行の"NB."以降はコメントになる。

名詞の書き方

Jには定数もいろいろある。その書き方を紹介しよう。

   12345    NB. 普通の整数
12345
   3.14    NB. 浮動小数点数
3.14
   2e5    NB. 指数表現(xey = x * 10^y)
200000
   1r2    NB. 分数表現(これは2分の1)
1r2
   4j3    NB. 複素数(これは4 + 3i)
4j3
   2b10101    NB. 基数指定(これは2進数で10101)
21
   3p2    NB. 円周率表現(これは3π^2)
29.6088

と、まあ本当にいろいろある。あと特殊なのは負の数の書き方かな。

   _5    NB. 負の数はアンダースコアをつける
_5
   3 - 5
_2

あとは特殊な定数たち。

   _    NB. アンダースコア単体だと「無限大」
_
   __    NB. アンダースコアにアンダースコアをつなげると「マイナス無限大」
__
   _.    NB. アンダースコアにドットで「不定形、未定義」
_.

文字列はシングルクォーテーションで囲む。文字列中にシングルクォーテーションを含ませたい場合はシングルクォーテーションを二つつづける。

   'Hello, world!'
Hello, world!
   'He said ''Hello.'''
He said 'Hello.'
演算の順序

普通の言語には演算子の優先順位ってものがあって、たとえば 4 * 3 + 2 は掛け算が優先されて 12 + 2 になって答は14になる。J言語ではどうだろう?対話環境に "4 * 3 + 2" を入れてみよう。

答は20になったはず。つまり掛け算は優先されていない。というか、Jには演算子の優先順位が存在しない

じゃあどういう順序で計算されるの? それは簡単。全部右から。だから 4 * 3 + 2 は 4 * (3 + 2) と解釈されるし、たとえば 1 - 2 - 3 は 1 - (2 - 3) と解釈される。

もし最初にどうしても左のほうを計算してほしいと思ったらそこは括弧でかこめばいい。これは普通だね。

   4 * 3 + 2
20
   (4 * 3) + 2
14
アレイの演算の基礎

さて、いままで見てきた例は全部アトムに対しての演算だった。次はランクが1以上のアレイに対する演算を見ていこう。

まずはアレイの書き方からいこう。基本的にはスペースで区切ってならべるだけだ。

   3 4 5
3 4 5

ランクが2以上のアレイを書くには整形のための動詞 $ を使う必要がある。

   2 3 $ 3 1 4 1 5 9
3 1 4
1 5 9

上の例では縦2、横3のテーブルをつくった。つまり左側のが作りたいアレイの大きさをあらわして、右側のがその要素をあらわすわけだ。

さて、アレイに対する演算は基本的にはまとめて行われる。どういうことかというと、

   1 2 3 * 2
2 4 6
   3 5 7 - 6
_3 _1 1
   3 * 2 3 $ 3 1 4 1 5 9
9 3 12
3 15 27

という風に、アレイとアトムの演算はアレイの各要素に対して行われる。また、アレイ同士の演算については

   1 2 3 + 3 4 5
4 6 8
   (2 3 $ 3 1 4 1 5 9) + (2 3 $ 2 7 1 8 2 8)
5 8 5
9 7 17

対応する要素同士にそれぞれ演算が行われる。アレイ同士の大きさが違うとエラーになるので気をつけよう。

   2 3 4 + 3 1
|length error
|   2 3 4    +3 1
代入

Jでももちろん変数は使える。それに自分で動詞なんかを定義することもできる。それには代入が必要になるが、Jの代入は2種類ある。

ひとつは動詞 =. で表され、もうひとつは動詞 =: で表される。

   a =. 5
   a * 3
15

このふたつの違いは何かというと、 =. が局所代入で =: が大局代入ということだ。簡単にいうと =. はローカル変数、 =: はグローバル変数を作ることができる。

ローカルとグローバルの違いは動詞を定義したときにあらわれる。すなわち、その動詞内だけで使う変数とそうでない変数の区別ができる。

動詞の定義の仕方については合成を勉強した後に教えることにしよう。

副詞の基礎

Jに用意されているほとんどの単語は動詞だが、副詞も重要だ。副詞は動詞に作用して、その動詞の処理に影響を与える。

書き方は簡単で、動詞の右隣に副詞をおけばいい。動詞 + に副詞 / を適用するには +/ だ。

さて、いまでてきた副詞 / はよく使う副詞なので説明しよう。この副詞には単項で使うときは「挿入」、二項で使うときは「表」という意味がある。

まず単項の場合をみてみよう。たとえば加算の動詞 + に単項で / を適用してみると

   +/ 1 2 3 4 5
15

のように総和を求めることができる。なぜこうなるか、というとそれは副詞 / が動詞 + をアレイ中に「挿入」しているからだ。

   +/ 1 2 3 4 5    NB. これは
15
   1 + 2 + 3 + 4 + 5   NB. こういう風な意味
15

次に二項の場合を見よう。

   1 2 3 +/ 4 5
5 6
6 7
7 8

これはつまり、左側のアレイの要素と右側のアレイの要素をそれぞれ加算した表を作っていることになる。これを使えば

   x =. 1 2 3 4 5 6 7 8 9
   x */ x
1  2  3  4  5  6  7  8  9
2  4  6  8 10 12 14 16 18
3  6  9 12 15 18 21 24 27
4  8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81

のように簡単に九九表をつくることができる。

動詞の合成

さて、いよいよ合成だ。Jにはひとつの動詞に単項と二項で2つの意味があるから、合成の規則はいろいろと考えられる。

実際、Jには動詞の合成規則がとてもたくさんある。動詞を合成するための接続詞だけで6種類もある。

そのたくさんある合成規則の中でも、Jに「言語としての機能」として組み込まれた規則がある。つまり、その規則は合成の接続詞なしで、ただ単に動詞を並べるだけで使うことができる。それが「トレイン」だ。

この規則は「フック」と「フォーク」という2つの規則から成り立っている。まずはこの2つについて説明しよう。

フックというのは2つの動詞を合成するときの規則だ。2つの動詞をf, gとしよう。合成後の動詞にも当然単項と二項、両方のはたらきがあるから両方紹介する。まずは単項の場合。

   (g f) x

   x g (f x)

と解釈される。つまり、xとf xをgの2つの引数とする。

次に二項の場合。

   x (g f) y

   x g (f y)

となる。つまり、xとf yをgの2つの引数とする。(修正: 2010/6/18 7:35)

次はフォークについて説明しよう。これは3つの動詞を合成するときの規則だ。3つの動詞をf, g, hとしよう。これもフックと同じように単項と二項がある。まずは単項の場合。

   (f g h) x

   (f x) g (h x)

と解釈される。二項の場合

   x (f g h) y

   (x f y) g (x h y)

と解釈される。

さて、フックとフォークで3つまでの動詞の合成規則を確認できた。トレインは一般に何個でも動詞を合成できる規則だが、その規則は「右から順にフックとフォークを適用していく」というものだ。

たとえば6つの動詞 a, b, c, d, e, f を合成すると

   a b c d e f
   a b c (d e f)   NB. 「フォーク」によって (d e f) が一つの動詞扱いになる
   a (b c (d e f))   NB. 「フォーク」によって(b c (d e f))が一つの動詞扱いになる
  (a (b c (d e f)))   NB. 「フック」によって (a (b c (d e f))) が一つの動詞扱いになる

という風になる。これがJの最も基礎的な動詞の合成規則トレインだ。

動詞合成の例

動詞合成の例として一番引き合いにだされるのはやっぱり「平均値を求める動詞」だ。ここで平均は「全部の要素の合計を要素数で割ったもの」すなわち算術平均。

あるアレイ x があったとして、その平均値を求めることを考えよう。

平均を求めるのに必要なことは「アレイの要素の合計を求めること」「アレイの要素数を求めること」「割り算をすること」の3つ。まずはこれらを別々に考えてみよう。

「アレイの要素の合計を求めること」これは副詞の項でやった +/ がそのまま使える。

「アレイの要素数を求めること」これにはそのための動詞 # があるのでそれを使う。

「割り算をすること」これには割り算の動詞 % を使う。

   x =. 7 1 4 3 9
   +/ x
24
   # x
5
   (+/ x) % (# x)
4.8

これでとりあえず平均を求めることはできた。しかしこの最後の式 "(+/ x) % (# x)" をよく見ると、単項のフォークの規則 "(f x) g (h x)" にそっくりだ。(f, g, h)を(+/, %, #) で置き換えればよい。

   (+/ % #) x
4.8
動詞の定義

さてそれでは先延ばしにしていた動詞の定義をしよう。動詞の定義には2種類ある。ひとつが明示的な定義(Explicit definition)で、もう一つが暗黙的な定義(Tacit definition)だ。

まず暗黙の定義から見ていこう。暗黙の定義のほうはそのまま、動詞を代入する感じで定義できる。

   plus =: +
   sum =: +/
   avg =: sum % #
   3 plus 5
8
   sum 2 3 4
9
   avg 7 1 4 3 9
4.8

ここで暗黙の定義では勝手に動詞が合成されていることに注意しよう。

次に明示的な定義。これは明示的に引数を書いてやることで定義できる。

   plus =: 4 : 'x + y'
   sum =: 3 : '+/ y'
   avg =: 3 : '(sum y) % (# y)'
   3 plus 5
8
   sum 2 3 4
9
   avg 7 1 4 3 9
4.8

ここで "3 :" というのは後に続くのは単項動詞だよ、ということを表すもので "4 : "の方が後に続くのは二項動詞だよ、ということを表すものだ。また右側にくる引数は自動的に y、左側にくる引数は自動的に x という名前になっている。

おわり

さて、これまででだいたいJの基本を説明できた。後は用意されている基本の単語を覚えるだけだ。

これでこの記事は終わりだけど、今後も基本の単語の説明とかJのテクニックについての記事はたくさん書くつもりなので、この記事を読んでくれた人は是非そっちも読んでほしい。