はじめに
この記事は @unsigned-wrong-wrong-int さんの記事の「if 文なしで じゃんけん J 言語版」の解説です。
J言語の基本
「そもそもJ言語ってなに?」っていう人がほとんどだと思うので、この記事で扱うJ言語の基本的な部分を先に解説します。
「基本は知ってるよ」、「メインの解説だけみたいよ」っていう人はメインの見出しまで飛ばしてください。
J言語はAPLの後継として提案されたプログラミング言語で、正式名称は「J」ですがC言語と同様に「J言語」と呼ばれています。
J言語は一言でいうと、とにかく短く書けるプログラミング言語です。1
様々な演算子によって、制御構文を使わず、自然言語を書くようにコードを書くことができます。
ちまたでは「J言語は難読言語」なんて言われてるみたいですが、文法を知れば普通に読めます。
対話形式
J言語は対話形式で使うことができます。入力プロンプトは半角スペース3つです。
この記事では対話形式を想定して使用例を載せるので、先頭にスペースのある行は入力だと思ってください。
1 + 2
3
echo 'Hello, World!'
Hello, World!
p:i.15
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
マイナスはアンダーバー
J言語ではリテラルのマイナス記号をアンダーバー( _
)で表します。2
$-1$ は _1
、 $-23$ は _23
で表します。
ハイフン( -
)はマイナス記号ではなく、引き算や符号を反転させる演算子として使います。
_5 + 7
2
7 - 13
_6
- 4
_4
- _6
6
ダブルクォーテーションは演算子
J言語の文字列リテラルはシングルクォーテーション( '
)で囲みます。
ダブルクォーテーション( "
)は演算子なので注意してください。
'Hello'
Hello
"Hello"
|syntax error
| "Hello"
右から左
J言語の計算は右から左に進みます。
掛け算、割り算が優先とか、そういうものはありません。カッコをつけて優先順位を変えることはできます。
2 + 7 * 1 - 8
_47
2 + 7 * _7
_47
2 + _49
_47
コメントは NB.
J言語の行コメントは NB.
で始まります。3
もう一度言います。J言語のコメントは NB.
です。慣れてください。
NB.
から改行までがコメントとして無視されます。
複数行コメントには Note
を使います。
これに何か引数を与えると、そこから単一の )
が書かれた行までコメントになります。4 5
与える引数は何でもいいので、コメントの概要や見出しを書いたりします。
1 + 1 NB. コメント
2
Note '複数行コメント'
複数行コメントは
長い説明を書くときに
使います。
)
J言語の品詞
J言語を構成する要素のカテゴリーのことを**品詞(Parts of Speech)**といいます。
J言語の品詞には自然言語と同じように、名詞、動詞、副詞、接続詞などがあります。
名詞
**名詞(Noun)**は数値、文字列、配列、ボックスといった値のことです。
空白区切りで要素を並べると配列になります。
ボックスについてはあとで説明します。
443
443
3.14
3.14
'Hello'
Hello
1 1 2 3 5 8 13 21 34 55 NB. 配列
1 1 2 3 5 8 13 21 34 55
<'box' NB. ボックス
+---+
|box|
+---+
動詞
**動詞(Verb)**は名詞を引数(Argument)として受け取って処理し、名詞の結果を返すものです。
ほかの言語でいうところの「関数」です。
NuVocで紫色になっているのが動詞です。
動詞の使い方は単項(Monad)と2項(Dyad)の2つあり、使い方によって動作が変わってきます。例えば、 -
は単項では符号の反転、2項では引き算をし、 %
は単項では逆数を求め、2項では割り算をします。
単項で使う場合は引数を動詞の右から1つ、2項で使う場合は左右から2つ渡します。
そして、動詞の処理は右から左に進みます。
- 3
_3
7 - 2
5
% 4
0.25
9 % 6
1.5
% 4.2 - % 5 NB. (% (4.2 - (% 5)))
0.25
副詞
**副詞(Adverb)**は1つのオペランド(Operand)を取る修飾詞(Modifier)です。
動詞や名詞をオペランドとして取り、動詞や名詞を作ります。ほかの言語でいうところの「高階関数」です。
NuVocで水色になっているのが副詞です。
例えば、副詞 /
はオペランドを配列の要素の間に埋め込むような処理をする新たな動詞を作ります。
オペランドは副詞の左に置きます。
+/ 1 2 3 4 5 NB. 1 + 2 + 3 + 4 + 5
15
*/ 1 2 3 4 5 NB. 1 * 2 * 3 * 4 * 5
120
-/ 1 2 3 4 5 NB. 1 - 2 - 3 - 4 - 5 (ここでも右から左)
3
接続詞
**接続詞(Conjunction)**は副詞と同じく修飾詞ですが、オペランドを左右から2つ取ります。
NuVocで黄緑色になっているのが接続詞です。
例えば、 @
と &
は右の動詞の実行毎に、その結果を左の動詞に渡すという処理をします。
2つの接続詞は似た動きをしますが、2項のときに計算方法が変わります。
- 単項で使うとき
f@g y
またはf&g y
→f (g y)
6 - 2項で使うとき
x f@g y
→f (x g y)
x f&g y
→(g x) f (g y)
処理の順序は2つとも右オペランド→左オペランドなのですが、2項で適用される動詞が異なります。 @
では右オペランドで、 &
では左オペランドで2項の計算が行われます。
%@- 5
_0.2
%&- 5
_0.2
6 %@- 10 NB. % (6 - 10)
_0.25
6 %&- 10 NB. (- 6) % (- 10)
0.6
また、接続詞や副詞を結合する処理は左から右に行われます。動詞の計算とは順序が逆なので注意してください。
J言語の変数
J言語では名詞、動詞などに**名前(Name)**をつけることができます。
名前はほかの言語でいうところの「変数」や「関数」です。
読みやすくするために、この記事では以降この「名前」のことを「変数」と呼びます。
変数は 変数名 =: 値
の形で定義します。7
変数名
の部分は文字列で示すこともできます。
a =: 123
b =: 'hello'
'c' =: 4.5
d =: 'e'
(d) =: 1 2 3 NB. d の格納する文字列 'e' をもとに変数 e を定義
e
1 2 3
f =: +
a f c NB. a + c
127.5
g =: /
f g 12 34 56 78 NB. + / 12 34 56 78
180
ボックス
J言語の便利な機能の1つに**ボックス(Box)**があります。
あらゆる名詞をボックスで包むことができます。数値でも、文字列でも、配列でも、さらにはボックスであってもです。
さらに、ボックスで包んだものはすべて同じデータ型として扱われるので、ボックスの配列を作ることができます。
そのかわり、中身を計算に使うときはボックスから出す必要があります。
ボックスで包むには動詞 <
の単項を、出すには動詞 >
の単項を使います。
a=: <567
a
+---+
|567|
+---+
b=: <'yeah'
b
+----+
|yeah|
+----+
<1 4 1 4 2 1 3 5 6
+-----------------+
|1 4 1 4 2 1 3 5 6|
+-----------------+
<<'box in box'
+------------+
|+----------+|
||box in box||
|+----------+|
+------------+
(<_1.23) , (<'abc') , (<1 7 3 2) , <<100
+-----+---+-------+-----+
|_1.23|abc|1 7 3 2|+---+|
| | | ||100||
| | | |+---+|
+-----+---+-------+-----+
>a
567
>b
yeah
>><<<'hey'
+---+
|hey|
+---+
また、動詞 ;
の2項はボックスで包みながら要素を結合することができます。
1 ; 2 ; 3
+-+-+-+
|1|2|3|
+-+-+-+
'Pi' ; 'is' ; 3.14159
+--+--+-------+
|Pi|is|3.14159|
+--+--+-------+
明示的な定義
J言語では自分で動詞や副詞、接続詞を作ることができます。
{{ }}
で囲んだ部分にJ言語の式を書くことで明示的(Explicit)な定義をすることができます。
ここで、左右の引数やオペランドをどう受けとるかですが、この定義のなかでだけ使える変数があります。
それが、 x y
u v
m n
の6つです。
x y
は動詞として引数を受けとるために使います。 x
が左からの引数、 y
が右からの引数です。
つまり、定義のなかで x
や y
を使うと、それは動詞として扱われます。単項の動詞を作るときは y
だけ使います。
u v
は副詞や接続詞としてオペランドを受けとるために使います。 u
が左のオペランド、 v
が右のオペランドです。
また、m n
は u v
と同じように使いますが、オペランドに名詞しか渡せなくなります。
u
もしくは m
のみを使うと、それは副詞として扱われます。それ以外は接続詞として扱われます。
plus=: {{ x + y }}
1 plus 2
3
double=: {{ 2 * y }}
double 3
6
and=: {{ v@u }}
1 plus and double 2
6
動詞の合成
明示的な定義とは別に、暗黙(Tacit)の定義というものもあります。
それが動詞の合成です。
動詞を並べることで合成し、新たな動詞を作ることができます。
フォーク
フォーク(Fork)は3つの動詞を合成する方法です。
丸カッコの中で動詞を3つ並べるだけでフォークになります。
フォークで作られた動詞では、次のような計算が行われます。
- フォークで作られた動詞を単項で使ったとき
(f g h) y
→(f y) g (h y)
- フォークで作られた動詞を2項で使ったとき
x (f g h) y
→(x f y) g (x h y)
左右の動詞の結果を中央の動詞に渡すという流れで計算されます。
次の例では配列の要素の平均値を求めています。
(+/ % #) 46 12 75 92 79
60.8
変数にフォークした動詞をそのまま代入する場合は、丸カッコは必要ありません。
avg=: +/ % #
avg 742 541 323 752 300 577 326 513 40 796
491
#
は要素数を求める動詞で +/
は総和を求める動詞、 %
は割り算の動詞です。
平均値の求め方は 総和 ÷ 要素数
なので、 (+/ % #)
というふうに並べることで、平均値を求める動詞ができます。
とても直感的に合成することができると思います。
また、左の動詞は名詞に置き換えることができます。
そのときの計算は次のようになります。
- 単項
(m g h) y
→m g (h y)
- 2項
x (m g h) y
→m g (x h y)
左の動詞の結果の部分を、名詞に置き換えることができます。
次の例では掛け算の結果を $998244353$ で割ったあまりを求めています。
modmulti=: 998244353 | *
123 modmulti 456
56088
12345678 modmulti 98765432
691143986
|
は右引数を左引数で割ったあまりを出す動詞です。
このように、フォークの左側には名詞を使うことができます。
右側に名詞を使うとフォークにはならず、そのまま計算が進みます。
フック
フック(Hook)は2つの動詞を合成する方法です。
この記事の解説では使用しないので詳しい説明は省略しますが、フォークと同じように作られ、次のように計算されます。
- 単項
(f g) y
→y f (g y)
- 2項
x (f g) y
→x f (g y)
rate=: % +/
rate 2 3 4 5 6
0.1 0.15 0.2 0.25 0.3
「if文なしでじゃんけん」
やっとメインの解説です。
「下準備」の解説
下準備の部分の解説をします。
ここでは必要な名詞、動詞、副詞を作成しています。
is=: 0 0 $ {{ (x) =: y }}
rps=: (< [ is~);._1' rock paper scissors'
vs=: ,
sign=: {{ {.m }}
beats=: {{
(x vs x) is 'あいこ'
(x vs y) is 'あなたの勝ち'
(y vs x) is 'あなたの負け'
}}
result=: {{
'[' , (".x sign) , ' vs ' , (".y sign) , '] ' , ".x vs y
}}
every=: {{ u@v&>/~ }}
of=: ]
is
is=: 0 0 $ {{ (x) =: y }}
is
は変数の定義をするための動詞です。
明示的な定義した動詞を、すぐにフォークで合成しています。
この動詞は、左引数の文字列を変数名とした変数を定義して、右引数の値を代入することができます。
=:
とほとんど同じことをしていますが、この形にすることで、英語に近い形で使えて可読性が上がるということと、動詞の合成を行えるようになるという利点があります。8
'pi' is 3.14
pi
3.14
まず {{ (x) =: y }}
で変数を定義します。
ここで x
にカッコをつけなければ、 x
という変数を定義してしまうことになります。
次に名詞・動詞・動詞のフォークの部分では、動詞 $
で {{ (x) =: y }}
の結果を $0$ 行 $0$ 列の行列(2次元配列)に変形します。この $0$ 行 $0$ 列の行列というのはJ言語で空として扱われ、出力しようとしても空行すら出力されません。
なので、動詞の結果がないときに用いられます。Python の None
のような扱いです。
rps
( rock
paper
scissors
)
rps=: (< [ is~);._1' rock paper scissors'
ここでは rps
という変数を定義しているように見えますが、じつは is
によって rock
paper
scissors
という3つの変数が一緒に定義されています。
rps
はボックスの配列の変数で、 rock
、 paper
、 scissors
は文字列の変数です。
rps=: (< [ is~);._1' rock paper scissors'
rps
+----+-----+--------+
|rock|paper|scissors|
+----+-----+--------+
rock
rock
paper
paper
scissors
scissors
;.
は文字列を分割する接続詞で、左には分割した文字列に適用する動詞、右には分割方法を数値で指定します。分割方法に _1
を指定すると、文字列の最初の文字を区切り文字として、区切り文字を取り除きながら分割します。
まず、文字列 ' rock paper scissors'
は 'rock'
'paper'
'scissors'
の3つの文字列に分割されます。
次に、それぞれの文字列に (< [ is~)
が適用されます。
~
は単項のとき、同じ引数を左にも渡し2項で計算するようにする副詞です。例えば、 is~ 'rock'
は 'rock' is 'rock'
として計算されます。
よって、自身の変数名を格納する変数が定義されます。ここで rock
paper
scissors
の3つの変数が定義されます。
また、 動詞 <
で3つの文字列はそれぞれボックスに入れられて、左引数をそのまま返す動詞 [
で <
の結果だけを取ります。 is~
は副作用だけが目的ということです。
最後に、それぞれボックスに入れられた文字列が結合されて rps
に代入されます。
vs
vs=: ,
vs
は配列や文字列を結合する動詞 ,
の別名として定義されています。
勝負の結果を格納する変数の変数名を作成するために使います。
sign
sign=: {{ {.m }}
sign
はオペランドの文字列の先頭の文字を返す副詞です。
副詞なので、その文字列と優先的に結合してくれます。
絵文字を格納する変数名を作成するために使います。
'rock' sign
r
'paper' sign ; 'scissors' sign
+-+-+
|p|s|
+-+-+
('paper' sign) ; ('scissors' sign)
+-+-+
|p|s|
+-+-+
beats
beats=: {{
(x vs x) is 'あいこ'
(x vs y) is 'あなたの勝ち'
(y vs x) is 'あなたの負け'
}}
beats
は勝ち負けの関係を表す変数を定義するための動詞です。
定義する変数名は、手を表す文字列を「あなた」、「相手」の順で繋げたものです。
左引数に勝つ手を表す文字列、右引数に負ける手を表す文字列を渡します。
'rock' beats 'scissors'
rockrock
あいこ
rockscissors
あなたの勝ち
scissorsrock
あなたの負け
result
result=: {{
'[' , (".x sign) , ' vs ' , (".y sign) , '] ' , ".x vs y
}}
result
は結果を示す文字列を作るための動詞です。
左右からそれぞれの手を示す文字列が渡されます。
x vs y
はそれぞれの手が勝負の結果を表す文字列を格納する変数名になる部分で、それは beats
によって定義されるものです。
x sign
と y sign
はそれぞれ絵文字を格納する変数名になる部分で、それは「実装」の部分で定義されます。
ここで使われている動詞 ".
はほかの言語でいうところの「eval」で、引数の文字列をJのコードとして実行して、その値を返します。
". '3 + 14'
17
z=: 159
". 'z'
159
every
every=: {{ u@v&>/~ }}
every
は渡された配列のすべての組み合わせに対して、ボックスを外して右オペランドに渡し、その結果を左オペランドに渡す接続詞です。
これは少し複雑なので、例を使って説明します。
u@v
は「 v
の処理結果を u
に渡して処理する」という意味です。
ここでは @
が使われているので、右オペランドを示す v
のほうが2項で計算されます。
画面への出力を行う動詞 echo
と配列を連結する動詞 ,
を使って例を示します。
z=: 'r';'p';'s'
z
+-+-+-+
|r|p|s|
+-+-+-+
z echo@, z
+-+-+-+-+-+-+
|r|p|s|r|p|s|
+-+-+-+-+-+-+
ボックスの配列が連結されたのち、出力されました。
&>
は「ボックスから出す毎に」という意味です。
上の例に付け加えてみます。
z echo@,&> z
rr
pp
ss
配列の要素がボックスから出されました。
そして >
のボックスから出す処理は要素ごとに行われるので、そのまま要素ごとに結合されて、要素ごとに出力されます。
/
は「左引数と右引数の要素を取るすべての組み合わせに対して処理する」という意味の副詞です。
z echo@,&>/ z
rr
rp
rs
pr
pp
ps
sr
sp
ss
すべての組み合わせに対して処理が行われました。
~
は「単項のとき、同じ引数を左にも渡し2項で処理する」という意味の副詞です。
これで左右から同じ名詞を渡したときと同じ処理をします。
echo@,&>/~ z
rr
rp
rs
pr
pp
ps
sr
sp
ss
このような処理が every
で結合した動詞で行われます。
名前の通り「すべてを処理する」といった感じです。
下の例の -every*
では「すべての掛け算に対して符号を反転する」という処理が行われています。
z=: 1 ; 2 ; 3 ; 4 ; 5
z
+-+-+-+-+-+
|1|2|3|4|5|
+-+-+-+-+-+
-every* z
_1 _2 _3 _4 _5
_2 _4 _6 _8 _10
_3 _6 _9 _12 _15
_4 _8 _12 _16 _20
_5 _10 _15 _20 _25
of
of=: ]
of
は右引数をそのまま返す動詞 ]
の別名として定義されています。
これはおそらく英語の文法にあわせて用意されたものです。
] 123
123
] 'hey'
hey
「実装」の解説
実装の部分の解説をします。
ここでは絵文字の設定や勝ち負けの定義、結果の出力を行っています。
rock sign is '✊'
paper sign is '🖐'
scissors sign is '✌'
rock beats scissors
paper beats rock
scissors beats paper
echo every result of rps
絵文字の設定
rock sign is '✊'
paper sign is '🖐'
scissors sign is '✌'
rock
は文字列 'rock'
です。 sign
は文字列の先頭の文字を出す副詞です。
つまり、 rock sign
は文字 'r'
になり、動詞 is
で変数 r
を作り文字列 '✊'
を代入しています。
同じように、変数 p
および s
を定義しています。
勝ち負けの定義
rock beats scissors
paper beats rock
scissors beats paper
グーはチョキに勝つ、パーはグーに勝つ、チョキはパーに勝つことを表す変数を定義します。
beats
の項目で解説した通りに変数が作られます。
結果の出力
echo every result of rps
すべてのじゃんけんの結果を出力します。
接続詞 every
によって echo
と result
が結合され、 rps
のすべての組み合わせに対する結果が求められて、それぞれ result
で作られる文字列を echo
が出力します。
echo every result of rps
[✊ vs ✊] あいこ
[✊ vs 🖐] あなたの負け
[✊ vs ✌] あなたの勝ち
[🖐 vs ✊] あなたの勝ち
[🖐 vs 🖐] あいこ
[🖐 vs ✌] あなたの負け
[✌ vs ✊] あなたの負け
[✌ vs 🖐] あなたの勝ち
[✌ vs ✌] あいこ
if文なしでじゃんけんできました。めでたしめでたし。
おわりに(布教)
長くなってしまいましたが、いかがだったでしょうか。
下準備の部分はJの演算子が多くあり、知らない人は分かりにくかったかもしれませんが、実装の部分はほとんど英語の意味の通りだったと思います。
J言語は初めて見ると、とても読みづらいと思うかもしれませんが、J言語は言語なのです。
日本語や英語と同じで様々な表現の仕方があります。そして、語彙を増やすことでより簡潔に分かりやすく表現することができるようになり、より多くの文章を読むことができるようになります。
使っていくうちに、ほかの言語と同じように使えるようになり、難読言語という感覚はなくなっていくはずです。私はJ言語で会話できます。
J言語はマイナーですが、短く単純な記述ができる文法や、強力な配列演算など、魅力あふれるプログラミング言語なのです!
やればやるほどハマる言語なので、ぜひハマってみてください!
ところで、競技プログラミングの問題を投稿できるサイト、MojaCoderでJ言語が使えるようになりました!
J言語は競技プログラミングでも力を発揮すると思っていたのですが、J言語に対応するサイトが全然なかったので、とてもありがたいです。
この機会に競プロerの皆さんにもぜひJ言語を知っていただきたいです!
もっと広まれJ言語!
-
Pythonなんかの比じゃありません!
Rubyが来たらちょっとやばいかもしれない。↩ -
_
単体では(正の)無限大を表します。負の無限大は__
です。 ↩ -
ラテン語の「Nota Bene(よく注意してね)」から来てるみたいです。 ↩
-
正確には複数行文字列です。Python の
""" … """
と同じ感じです。 ↩ -
閉じかっこを使うのは、 APL でエスケープ文字として使っていたなごりです。 ↩
-
これは計算のイメージです。実際にこのように展開されるわけではありません。 ↩
-
=.
というのもありますが、今回は使わないので省略します。 ↩ -
=:
は連結詞(Copula)と呼ばれる、動詞や副詞などとは違う特殊なものです。 ↩