tenderloveさんが発表されていた摩訶不思議な dilute.rb の原理について
発表では種明しなしでモヤモヤが止まらないので調べてみました。
動画はこの辺りから。
【招待講演】Image Recognition and Code that shouldn't exist Aaron Patterson氏
デモ概要
まずは何の変哲もない fibonacci スクリプトがあります。
次にこれに dilute.rb をかませると、スクリプトの一部が空白に置き換わりますが
最後に pipe で ruby に渡すと fib.rb を実行した時と同様の結果になります。
え?どういうこと?
当日会場で見てたのですが、さっぱりわかりませんでした。
あまりにわからないので
ググってみると以下の記事を見つけました。
Demystifying @tenderlove's homeopathic code optimizations
どうやら、新ネタではなく 2014年の別カンファレンスでの発表のようです。
そしてヒントとなるコードが示されていました。
#! /usr/bin/env ruby probability = $stdout.tty? ? (ENV["D"] || 10).to_i : 0 puts ARGF.read.chars.map { |c| rand(100) + 1 <= probability && c !~ /\s/ ? " " : c }.join
$stdout.tty?
標準出力が terminal かどうかを判定することができます。
これは初めて知りました。なるほど、これを利用していたのですね。
catを使って確認すると、以下のようになるわけです。
$ cat a.rb puts "stdin : #{$stdin.tty?}" puts "stdout: #{$stdout.tty?}"
$ ruby ./std_example.rb stdin : true stdout: true $ cat ./std_example.rb | ruby stdin : false stdout: true $ ruby std_example.rb | cat stdin : true stdout: false
少し足りない
上記コードは、一見 dilute されていますが、dilute を複数回実行した時も
1回実行した時と同様の動作となっています。
つまり、dilute率が20%なら 5回実行すると全て空白で置き換えられなければなりません。
何回目の実行なのか(または、前回のdilute率)というような値をどこかに保持しないいけません。
どこに持たせるのか悩んだのですが、結局、標準出力に含めちゃえばいいのかと気づいて
以下のようなコードになりました。
できた
#!/usr/bin/env ruby data, called_count = ARGF.read.split("\t") called_count = called_count.to_i + 1 if $stdout.tty? probability = (ENV['D'].to_i | 20) * called_count puts data.chars.map { |c| rand(100) + 1 <= probability && c !~ /\s/ ? ' ' : c }.join else puts "#{data}\t#{called_count}" end
$ ./dilute.rb fib.rb ef ib f n 3 1 else f b( -1 + fib(n-2 end e d p fib(3 $ ./dilute.rb fib.rb | ./dilute.rb e fib n f n < else b( -1 f (n-2) end e fib 33 $ ./dilute.rb fib.rb | ./dilute.rb | ./dilute.rb | ./dilute.rb | ./dilute.rb $ ./dilute.rb fib.rb | ./dilute.rb | ./dilute.rb | ./dilute.rb | ./dilute.rb | ruby 3524578