Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

イベントTシャツを支える技術 ー Acme::Bleachの詳解

こんにちは。エンジニアのid:kfly8です。

技術カンファレンスのノベルティで、コードを載せたデザインってイイですよね。謎解き要素で遊び心をくすぐりつつ、デザイン的にも普段使いしやすくかっこいいんですよね。リブセンスさんから2015年にもらったトートバッグなんかは、未だに使っています。

hiragram.hatenablog.jp

そんなノベルティを一度でいいから作ってみたかったんです……

f:id:kfly8:20220212222142p:plain

つくりました!!!!

手前味噌ですが、最初にデザインを見せてもらったとき、ビビビときました。カッコイイ。ビ○ムスさんとか街にある服屋さんにあっても、わからない。多分。一目惚れでした。

YAPC::Japan::Online 2022のロゴは、結び目がモチーフでどこか繋がりを感じるデザインです。結び目から着想した糸を34行のコードの上にあしらい、見かたによってはケーブルコードにも見えるので、”コードにコード”をかけたダジャレにもなっている。いやー本当に最高ですね!

はい。


さてイベント T シャツに書かれたこのコード。解読いただけたでしょうか?この後に続く解説を読むことなく、解読した方は個人的にお寿司を奢るので教えてください。

f:id:kfly8:20220304234817p:plain:w400
美味しいお寿司の写真です

このTシャツを見てみると、赤い点が並んでいます。このままでは意味がわかりません。

しかし、コードに何が書いてあるかわからないまま、このTシャツを着たら気持ちわるいですよね!変なメッセージかもしれません(そんなことはありません)

気持ちよく着れるように責任を持って解説をします。この後に続く解説を読めば誰でも30分くらいで解読できるようになります。個人差はあると思うので、予めご了承ください。

Acme::Bleachとは?

f:id:kfly8:20220304234953p:plain
一行目に書かれた Acme::Bleach

コードの1行目に、Acme::Bleachとあります。Acme::Bleachというと、次のようなプリント文が書かれたコードを、ホワイトスペースだけのコードに変換してしまうジョークモジュールです。

f:id:kfly8:20220302220413g:plain
コードが”漂白”されても動くだと・・?!

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幅の空白はタブだとわかります。行末の鍵マークは、改行です。文字が判別できるようになりました。

次に各行がなんと書かれているか判別をします。

f:id:kfly8:20220304234953p:plain
3行目は`space tab tab space tab tab space tab space`

「ヒント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が登壇しました。

tech.mobilefactory.jp

モバイルファクトリーでは、エンジニアのカジュアル面談を実施しています。PerlやTypeScriptのお仕事に興味を持っていただけなら、ぜひお気軽にご連絡ください。

カジュアル面談のお申し込み

あわせて、こちらの採用サイトもご覧ください。

recruit.mobilefactory.jp