Atsumaru Engineer's Blog

集客プラットフォーム事業を手がける株式会社あつまるのエンジニアブログです

JavaScript で Hello,world! に挑戦 (ただし記号だけを使って)

f:id:atmr:20190126144008p:plain

どうも、趣味で JavaScript を書いている三井です。

先日、 JavaScript つまみ食い LT 会 というイベントを主催しまして。
そのメインの LT 会後の懇親会で「懇親会 LT」と銘打ったゆるゆる LT をやりました。

私も参加して「JavaScript で Hello,world! に挑戦」という話をしたんですが、その資料が Web に上がっていないので(というか資料無しで話したので)この場を借りて話した内容をまとめてみます。


何をやる?

ブラウザに Hello,world! を表示させる JavaScript プログラムを 記号だけ 使って書きます。

プログラムを学び始めた人がまず最初に取り組む課題が Hello,world! を表示するだけのプログラムを書くというもの。
いまいちど初心に立ち返って記号だけで Hello,world! を書けば、 JavaScript についてより深く知ることができるんじゃないでしょうか(適当)


ご注意

この JavaScript 記号プログラミングは散々語り尽くされた分野です。
パイプライン演算子 |> を取り入れたこれからの記号プログラミングみたいな目新しい話題は出てきません。
それでもいいよという方だけご笑納ください。


では、さっそく取り組んでいきましょう。

まずは素振りしてみる

まずは記号だけで何ができるのか軽く素振りして確認してみましょう。

おなじみの空配列は、

[]

+ 演算子をつけることで数として評価されて、

+[]  // 0

このように 0 に化けます。

また、~ 演算子で補数を取ると、

~[]  // -1

0 以外の数を作ることができます。

なので、

    +[]  // 0
    ~[]  // -1
   -~[]  // 1
  ~-~[]  // -2
 -~-~[]  // 2
~-~-~[]  // -3
...

このように任意の整数を作ることができます。


続いて、

先ほどの空配列 []! 演算子で論理反転させると、

 ![]  // false
!![]  // true

truefalse が得られました。

さらに、 truefalse を空文字列 '' と結合すれば、

''+ ![]  // 'false'
''+!![]  // 'true'

文字列としての 'true''false' が得られました。


整数と文字列が手に入ったので、文字列を演算してみます。

ご存知の通り、

'false'[1]  // 'a'

ですので、

(''+![])[-~[]]  // 'a'

このように書くことで文字列の 'a' を取り出すことができました。


いかがでしょう。記号フログラミングの大いなる可能性を感じていただけましたね?


Hello,world! する

では Hello,world! しましょう。

まずは普通に、

alert('Hello,world!')

これを変形していきます。

使えるものが記号しかない都合上、ほとんどの組み込み関数はそのまま使うことができません。
なので eval() を使って書き直します。

eval("alert('Hello,world!')")

eval() はプログラムを 文字列として 与えてやればなんでも実行してくれる心強いやつです。
ですが、記号プログラミングでは eval() すら自由に使うことができません。代わりに関数コンストラクタ Function() で置き換えます。

Function("alert('Hello,world!')")()

関数コンストラクタ Function() は文字列を与えてインスタンス化すると eval() 相当の働きをする関数を返してくれます。
しかも new 演算子をつけずに関数として呼び出したときにも new 演算子でインスタンス化したときと同様に振る舞うという 雑な 力強い仕様です。


Function() を調達する

Function() をどこからともなく持ってきましょう。
空配列 []constructor を参照してみます。

[].constructor
// function Array() { [native code] }

もういっちょ

[].constructor.constructor
// function Function() { [native code] }

というわけで空配列 [] から Function() を得ることができました。


上記をふまえて alert('Hello,world!')

[].constructor.constructor("alert('Hello,world!')")()

というように変形できます。

さらにさらにメソッド呼び出しをドット演算子 . からブラケット記法 [] に置き換えておきます。

[]['constructor']['constructor']("alert('Hello,world!')")()

これです。
プログラムの中に 記号と文字列しかない 状態にまで変形できたことがお分りでしょうか。


よく使うフレーズを変数に用意しておく

と言うわけで、残る文字列部分を記号に置き換えていきたいんですが。
その前に、よく登場するフレーズを変数に入れて用意しておきましょう。

まずは 'constructor' を。

(''+{})[-~-~-~-~-~[]]     // c
+(''+{})[-~[]]            // o
+(''+[][[]])[-~[]]        // n
+(''+![])[-~-~-~[]]       // s
+(''+!![])[+[]]           // t
+(''+!![])[-~[]]          // r
+(''+[][[]])[+[]]         // u
+(''+({}))[-~-~-~-~-~[]]  // c
+(''+!![])[+[]]           // t
+(''+({}))[-~[]]          // o
+(''+!![])[-~[]]          // r

このように記号化しまして変数に入れます。

記号の中で変数名として使えるんのは $_ ですね。
'constructor'_ に代入します。

_=(''+{})[-~-~-~-~-~[]]+(''+{})[-~[]]+(''+[][[]])[-~[]]+(''+![])[-~-~-~[]]+(''+!![])[+[]]+(''+!![])[-~[]]+(''+[][[]])[+[]]+(''+({}))[-~-~-~-~-~[]]+(''+!![])[+[]]+(''+({}))[-~[]]+(''+!![])[-~[]]

はい。

次、 Function()_$ に代入します。

_$=[][_][_]

簡単ですね。

それから、気まぐれに 'return "\u' を用意して、

(''+!![])[-~[]]               // r
+(''+!![])[-~-~-~[]]          // e
+(''+!![])[+[]]               // t
+(''+!![])[-~-~[]]            // u
+(''+!![])[-~[]]              // r
+(''+[][[]])[-~[]]            // n
+(''+{})[-~-~-~-~-~-~-~[]]    // ' '
+'"\\'                        // "\
+(''+[][[]])[+[]]             // u

変数 __ に代入します。

__=(''+!![])[-~[]]+(''+!![])[-~-~-~[]]+(''+!![])[+[]]+(''+!![])[-~-~[]]+(''+!![])[-~[]]+(''+[][[]])[-~[]]+(''+{})[-~-~-~-~-~-~-~[]]+'"\\'+(''+[][[]])[+[]]

これで役者は揃いました。


プログラム全体を記号化

ではいよいよ、

[]['constructor']['constructor']("alert('Hello,world!')")()

を記号だけで置き換えて、

// 'constructor'
_=(''+{})[-~-~-~-~-~[]]+(''+{})[-~[]]+(''+[][[]])[-~[]]+(''+![])[-~-~-~[]]+(''+!![])[+[]]+(''+!![])[-~[]]+(''+[][[]])[+[]]+(''+({}))[-~-~-~-~-~[]]+(''+!![])[+[]]+(''+({}))[-~[]]+(''+!![])[-~[]]

// Function()
_$=[][_][_]

// 'return "\u'
__=(''+!![])[-~[]]+(''+!![])[-~-~-~[]]+(''+!![])[+[]]+(''+!![])[-~-~[]]+(''+!![])[-~[]]+(''+[][[]])[-~[]]+(''+{})[-~-~-~-~-~-~-~[]]+'"\\'+(''+[][[]])[+[]]

// alert('Hello,world!')
// -> eval("alert('Hello,world!')")
// -> Function("alert('Hello,world!')")()
// -> []['constructor']['constructor']("alert('Hello,world!')")()
[][
  _  // 'constructor'
][
  _  // 'constructor'
](
  (''+![])[-~[]]                                                   // a
  +(''+![])[-~-~[]]                                                // l
  +(''+![])[-~-~-~-~[]]                                            // e
  +(''+!![])[-~[]]                                                 // r
  +(''+!![])[+[]]                                                  // t
  +"('"                                                            // ('
  +_$(__+(+[])+(+[])+(-~-~-~-~[])+(-~-~-~-~-~-~-~-~[])+'"')()      // H
  +(''+!![])[-~-~-~[]]                                             // e
  +(''+![])[-~-~[]]                                                // l
  +(''+![])[-~-~[]]                                                // l
  +(''+{})[-~[]]                                                   // o
  +','                                                             // ,
  +_$(__+(+[])+(+[])+(-~-~-~-~-~-~-~[])+(-~-~-~-~-~-~-~[])+'"')()  // w
  +(''+({}))[-~[]]                                                 // o
  +(''+!![])[-~[]]                                                 // r
  +(''+![])[-~-~[]]                                                // l
  +(''+[][[]])[-~-~[]]                                             // d
  +_$(__+(+[])+(+[])+(-~-~[])+(-~[])+'"')()                        // !
  +"')"                                                            // ')
)()

さらに minify して、

_=(''+{})[-~-~-~-~-~[]]+(''+{})[-~[]]+(''+[][[]])[-~[]]+(''+![])[-~-~-~[]]+(''+!![])[+[]]+(''+!![])[-~[]]+(''+[][[]])[+[]]+(''+({}))[-~-~-~-~-~[]]+(''+!![])[+[]]+(''+({}))[-~[]]+(''+!![])[-~[]];_$=[][_][_];__=(''+!![])[-~[]]+(''+!![])[-~-~-~[]]+(''+!![])[+[]]+(''+!![])[-~-~[]]+(''+!![])[-~[]]+(''+[][[]])[-~[]]+(''+{})[-~-~-~-~-~-~-~[]]+'"\\'+(''+[][[]])[+[]];[][_][_]((''+![])[-~[]]+(''+![])[-~-~[]]+(''+![])[-~-~-~-~[]]+(''+!![])[-~[]]+(''+!![])[+[]]+"('"+_$(__+(+[])+(+[])+(-~-~-~-~[])+(-~-~-~-~-~-~-~-~[])+'"')()+(''+!![])[-~-~-~[]]+(''+![])[-~-~[]]+(''+![])[-~-~[]]+(''+{})[-~[]]+','+_$(__+(+[])+(+[])+(-~-~-~-~-~-~-~[])+(-~-~-~-~-~-~-~[])+'"')()+(''+({}))[-~[]]+(''+!![])[-~[]]+(''+![])[-~-~[]]+(''+[][[]])[-~-~[]]+_$(__+(+[])+(+[])+(-~-~[])+(-~[])+'"')()+"')")()

完成です!


実行結果

f:id:atmr:20190126142817p:plain
Chrome のデベロッパーツールから実行した結果

はい、


まとめ

お疲れさまでした。
ここまで読み進められた方ならば JavaScript の記号プログラミングの魅力を少しでも感じ取っていただけたんではないでしょうか。


私からは以上です。


おまけ

実は今回のような制限の中でプログラムを書く遊びは言語を問わずに様々にあるものでして。

例えば Ruby でも記号だけを使ったプログラムが可能です。


私が知る限りこのような遊びの中でも古いものに perl の 予約語 だけを使ったプログラムがあります。
ppencode というキーワードで検索するといろいろな記事が見つかると思います。


ppencode 発案者は @TAKESAKO さんという方で、自らが ppencode について解説したセッションが動画で残っているので興味のある方はぜひ見てみてください。
perl が読めない書けない人でも ppencode の面白さ・ワクワク感が伝わってくる良いセンションだと思います。


それでは、Enjoy!! ;)