こんにちは。エンジニアのid:kfly8です。
技術カンファレンスのノベルティで、コードを載せたデザインってイイですよね。謎解き要素で遊び心をくすぐりつつ、デザイン的にも普段使いしやすくかっこいいんですよね。リブセンスさんから2015年にもらったトートバッグなんかは、未だに使っています。
そんなノベルティを一度でいいから作ってみたかったんです……
つくりました!!!!
手前味噌ですが、最初にデザインを見せてもらったとき、ビビビときました。カッコイイ。ビ○ムスさんとか街にある服屋さんにあっても、わからない。多分。一目惚れでした。
YAPC::Japan::Online 2022のロゴは、結び目がモチーフでどこか繋がりを感じるデザインです。結び目から着想した糸を34行のコードの上にあしらい、見かたによってはケーブルコードにも見えるので、”コードにコード”をかけたダジャレにもなっている。いやー本当に最高ですね!
はい。
さてイベント T シャツに書かれたこのコード。解読いただけたでしょうか?この後に続く解説を読むことなく、解読した方は個人的にお寿司を奢るので教えてください。
このTシャツを見てみると、赤い点が並んでいます。このままでは意味がわかりません。
しかし、コードに何が書いてあるかわからないまま、このTシャツを着たら気持ちわるいですよね!変なメッセージかもしれません(そんなことはありません)
気持ちよく着れるように責任を持って解説をします。この後に続く解説を読めば誰でも30分くらいで解読できるようになります。個人差はあると思うので、予めご了承ください。
Acme::Bleachとは?
コードの1行目に、Acme::Bleachとあります。Acme::Bleachというと、次のようなプリント文が書かれたコードを、ホワイトスペースだけのコードに変換してしまうジョークモジュールです。
Perl Best Practiceなどで知られるDamian Conway先生が書いた最初のAcmeモジュール*1です。Acmeモジュールというと、makamakaさん曰く"一見何の役に立つかわからないけれども、やはり実際役に立たない"*2…はずなのに、今回、Tシャツ作りで役立ってしまいました。Thank you very much, Dr. Damian.
Acme::Bleachの動作原理はシンプルです。
まずコード文字列をビット列に変換し、次にビット列の0をスペースに、1をタブに変換し、ホワイトスペースだけのコードに「漂白」をします。そして「漂白」されたコードを逆変換して元のコードに戻します。大雑把に言えば、スペースとタブでビット列を表しているのが肝です。
この動作原理を利用すれば、.(ドット)と-(ハイフン)を利用し、モールス信号っぽく変換するAcme::MorseといったAcmeモジュールの意味も理解しやすくなると思います。
しかし、まだ、このTシャツのコードを読み解くことはできません。 読み解くには、この動作原理に加え、次の2つのヒントが必要になります。
- ヒント1: コードの先頭に、” \t(スペース+タブ)x8”の「ネクタイ」がつけられること
- ヒント2: 9文字区切りで改行されること
Acme::Bleachのコードは16行だけなので、ここから詳解します。もしイベントTシャツの意味が早く知りたい場合は、動作原理とこの2つのヒントを念頭に置いて、読み飛ばしてください。
Acme::Bleachの詳解
Acme::Bleachのコードは以下の通りです。一つ一つ解説をしましょう。
1: package Acme::Bleach; 2: our $VERSION = '1.150'; 3: my $tie = " \t"x8; 4: sub whiten { local $_ = unpack "b*", pop; tr/01/ \t/; s/(.{9})/$1\n/g; $tie.$_ } 5: sub brighten { local $_ = pop; s/^$tie|[^ \t]//g; tr/ \t/01/; pack "b*", $_ } 6: sub dirty { $_[0] =~ /\S/ } 7: sub dress { $_[0] =~ /^$tie/ } 8: open 0 or print "Can't rebleach '$0'\n" and exit; 9: (my $shirt = join "", <0>) =~ s/(.*)^\s*use\s+Acme::Bleach\s*;\n//sm; 10: my $coat = $1; 11: my $pressed = '#line ' . ("$coat\n" =~ tr/\n/\n/) . ' ' . (caller)[1] . "\n"; 12: local $SIG{__WARN__} = \&dirty; 13: do {eval $coat . brighten $shirt; print STDERR $@ if $@; exit} 14: unless dirty $shirt && not dress $shirt; 15: open 0, ">$0" or print "Cannot bleach '$0'\n" and exit; 16: print {0} "${coat}use Acme::Bleach;\n", whiten $pressed.$shirt and exit;
3行目: my $tie = " \t"x8;
スペースとタブでできたネクタイです。後ほど、シャツにネクタイを結ぶ、といったくだりがでてきます。
4行目: sub whiten { local $_ = unpack "b*", pop; tr/01/ \t/; s/(.{9})/$1\n/g; $tie.$_ }
これは改行を加えると次のようになります。
sub whiten { local $_ = unpack "b*", pop; tr/01/ \t/; s/(.{9})/$1\n/g; $tie.$_ }
これはコードを漂白をする関数です。呼び出している箇所を見ると第一引数に渡されるのは”シャツ”と呼ぶコード文字列です。
つまりシャツをクリーニングする関数です。シャツと呼ぶコード文字列を、unpack “b*”
して、ビット列にします。
デフォルト引数の$_
にいれ、この後に続く置換を省略して書けるようにしています。
tr/01/ \t/
で、0をスペースに、1をタブに置換しています。
s/(.{9})/$1\n/g
で、9文字区切りで、改行コードをいれています。
$tie.$_
で、ネクタイを先頭に結びます。
5行目: sub brighten { local $_ = pop; s/^$tie|[^ \t]//g; tr/ \t/01/; pack "b*", $_ }
これも改行を入れると、次のようになります。
sub brighten { local $_ = pop; s/^$tie|[^ \t]//g; tr/ \t/01/; pack "b*", $_ }
s/^$tie|[^ \t]//g
で、ネクタイと改行コードを取り除きます。
tr/ \t/01/
で、半角スペースを0に、タブを1に置換し、ビット列にします。
pack "b*", $_
で、ビット列を文字列に戻します。
漂白されたコードに光をあて、元のコード文字列を浮き上がらせます。
6行目: sub dirty { $_[0] =~ /\S/ }
ホワイトスペース以外に一致した数を返します。漂白されていない文字は汚いとみなしています。
7行目: sub dress { $_[0] =~ /^$tie/ }
ネクタイを結んでいるかどうか確認し、漂白済みかどうかの判定に使います。 コードが、ドレスコードを満たしているかどうかといった命名でしょうか。洒落てますね。
8行目: open 0 or print "Can't rebleach '$0'\n" and exit;
open 0
は、実行されているプログラム自身を、openしようとしています。1引数のopenはレガシーな使い方で、open HOGE
のようにファイルハンドラにHOGEを指定すると、同名の$HOGE
というパッケージ変数に指定されているファイルパスをopenしようとします。open 0
であれば、$0
を開こうとします。$0
はPerlの特殊変数で実行されているプログラム自身を指します。
9,10行目: (my $shirt = join "", <0>) =~ s/(.*)^\s*use\s+Acme::Bleach\s*;\n//sm;my $coat = $1;
my $shirt = join "", <0>
で、ファイルの中身を読み込みます。
そして読み込んだファイルを、
use Acme::Bleach;
より手前に書かれたコードを、$coat
use Acme::Bleach;
以降に書かれたコードを、$shirt
と命名しています。
11行目: my $pressed = '#line ' . ("$coat\n" =~ tr/\n/\n/) . ' ' . (caller)[1] . "\n";
"$coat\n" =~ tr/\n/\n/
で、 use Acme::Bleach;が書かれた行数を算出しています。
(caller)[1]
はファイル名です。
つまり、use Acme::Bleach;
と書かれた行を、“#line 行数 ファイル名”といったコメント行にします。
12行目: local $SIG{__WARN__} = \&dirty;
漂白されたコードで警告を抑制します。
13,14行目: do { ...中略 } unless dirty $shirt && not dress $shirt;
このdoブロックを整形すると次のようなコードになります。
do { eval $coat . brighten $shirt; print STDERR $@ if $@; exit } unless dirty $shirt && not dress $shirt;
漂白済みのシャツであれば、brightenで元々のコード文字列に戻し、evalで実行しています。
15行目: open 0, ">$0" or print "Cannot bleach '$0'\n" and exit;
実行ファイル自身を上書きするためのファイルハンドラを用意します。
16行目: print {0} "${coat}use Acme::Bleach;\n", whiten $pressed.$shirt and exit;
漂白されていないシャツを、漂白してファイルを上書きをしています。
詳解補足
初見では、コードの圧縮や特殊変数や意図のよくわからない変数名で読みにくいと思います。 けれど、動作原理を理解し、丁寧に読むと「コードを、上着とシャツにわける」「クリーニングしたシャツにネクタイを締める」「いらない行はアイロンでプレス」「ネクタイを締めているかどうかがドレスコードになっている」などなど紳士服を思わせる粋なコードです。
Tシャツに書かれたコードは、TMTOWTDI
まず、2行目以降の謎の赤い点は何でしょうか? Acme::Bleach はスペースとタブに「漂白」をするので、赤い点はスペースかタブのどちらかと推測できます。
「ヒント1: コードの先頭に、” \t(スペース+タブ)x8”のネクタイがつけられること」から、2行目の1文字目はスペースだと確定します。これにより「赤い点はスペース」と予想できます。赤い点がスペースだとわかれば約1.5cm幅の空白はタブだとわかります。行末の鍵マークは、改行です。文字が判別できるようになりました。
次に各行がなんと書かれているか判別をします。
「ヒント2: 9文字区切りで改行」から、例えば、3行目は、赤い点が4つあり、タブの数は9-4=5個 と判別できます。タブが1.5cm幅なので、定規をあてつつ測れば、space tab tab space tab tab space tab space
とわかります。簡単ですね。
この要領で、34行分数えます。根気のいる作業ですが、鍛えられた目grep力でがんばりましょう! 転写する時、スペースは0に、タブは1と記載すると、ホワイトスペースより見やすく、可読性が上がります。
0101010101010101110001000 011011010 010110011 101101010 011000000 100100011 000000010 000101010 101100100 010101011 110010111 010100010 101000100 010100100 100111010 000001110 001101100 101000000 001110010 011101001 011001110 110001011 100000010 001000100 001010101 011001000 101010111 100101110 101000101 010001000 101001001 001000100 01010000
このビット列から改行とネクタイを除去して、packすると次のコードが見えます。
perl -pe 's/\n//g; s/^0101010101010101//;' hoge.pl | perl -pe '$_ = pack "b*", $_' #line 1 TMTOWTDI.pl print "TMTOWTDI"
解読できました✌✌✌ これなら安心して着れそうですね!
まとめ
このTシャツには、Perlのスローガンの"There's More Than One Way To Do It." (やり方はひとつじゃない) 、略してTMTOWTDIが込められていました。弊社のエンジニアはもちろん、YAPCに参加される多くの方が好きな言葉の1つだと思います*3。YAPC楽しいですね!!!!
宣伝
弊社からは、TypeScriptのリプレースの話でkimusonが登壇しました。
モバイルファクトリーでは、エンジニアのカジュアル面談を実施しています。PerlやTypeScriptのお仕事に興味を持っていただけなら、ぜひお気軽にご連絡ください。
あわせて、こちらの採用サイトもご覧ください。
*1:https://gihyo.jp/dev/serial/01/perl-hackers-hub/001901?page=1
*2:https://b.hatena.ne.jp/entry/s/gihyo.jp/dev/serial/01/perl-hackers-hub/001901
*3:個人的には、TMTOWTDIの哲学は好きですが、コードを書く時は、TIMTOWTDIBSCINABTEが好き。https://blog.urth.org/2011/03/23/reviewing-perl-best-practices-chapter-15-objects