Ruby 1.9 の新機能のひとつに「lambda { ... } を -> { ... } と書ける」というのがあります。この表記は反対意見が根強い *1 ですが、確実にすばらしい点があって、全部記号だということです。これによって Ruby が記号だけでチューリング完全になります *2 。
デモとして、brainfuck インタプリタを記号だけで書いてみました。
$___,@_,@__,$_=(@@__="")=~//,?#=~/$/,->(_){_<(__="####"=~/$/)**__&&(@@__<< _;@__[_+@_])},[*$<]*@@__;@__[$___];$____,$_,@___,$__,@__=$_[@_+($_+?!=~/!/ )..-@_],$`,[],[],->(_){(__=$_[_];__=~/[><+\-\.,]/?$__<<$_[_]:__==?[?(@___, $__=$__,[]):__==?]?$__=@___<<$__:__==$\?$\:_)&&@__[_+@_]};@__[$___];@___, $_,@@_,@__=[],[],$___,->(_){$_[@@_]||=$___;({?>=>->{@@_+=@_},?<=>->{@@_-= @_},?+=>->{$_[@@_]+=@_},?-=>->{$_[@@_]-=@_},?.=>->{$><<@@__[$_[@@_]]},?,=> ->{$____=~/^./&&($____=$';$_[@@_]=@@__=~/#{((__,=[*?/..?:]&[$&];__)||(__,= [*?@...?[]&[$&];__)||(__,=[*?`...?{]&[$&];__))&&__!=?{?$&:'\\'+$&}/)},$\=> ->{$\}}[$__[_]]||->{$_[@@_]!=$___&&(@___<<[$__,_-@_];$__=$__[_];@__[$___] *@___,($__,_)=@___);""})[]&&@__[_+@_]};@__[$___]
このとおり。
$ cat hello.bf +++++++++[>++++++++>+++++++++++>+++++<<<-]>.>++.+++++++..+++.>-. ------------.<++++++++.--------.+++.------.--------.>+. $ ./ruby punctfuck.rb hello.bf; echo Hello, world!
プロの記号ゴルファーには常識かもしれませんが、ポイントだけ紹介します。
標準入力を読む方法
- $< が標準入力の IO です (正確には ARGF) 。
- $<.to_a で読み込んだ各行を各要素にしたリストが得られます。
- 1.9 では [*ary] で ary.to_a と同じような効果があります。
- Array#* は Array#join と同じです。
以上をまとめて、
[*$<]*""
で $<.read と同じ文字列が得られます。
標準出力に書く方法
1.8 では $ > < < 65 とかで "A" が出力されていましたが、1.9 では "65" が出力されてしまいます。なので dict = "\x00\x01\x02...\xff" という文字列を作って、$ > < < dict[65] とする必要があります。dict を作るのは、普通に書けば
dict = "" (0..255).each {|i| dict << i }
という感じですが、当然こんな風には書けないので、再帰で初期化します。
dict = "" func = lambda {|i| (dict << i; func.call(i + 1)) if i < 256 } func.call(0)
lambda は -> にして、Proc#call は Proc#[] にして、
dict = "" func = ->(i) { (dict << i; func[i + 1]) if i < 256 } func[0]
256 は 4**4 にして、後置 if は && にして、
dict = "" func = ->(i) { i < 4**4 && (dict << i; func[i + 1]) } func[0]
数字を String#=~ にして、
dict = "" func = ->(i) { i < ("####"=~/$/)**("####"=~/$/) && (dict << i; func[i + ("#"=~/$/)]) } func[""=~//]
変数名を全部記号にして、
_ = "" __ = ->(___) { ___ < ("####"=~/$/)**("####"=~/$/) && (_ << ___; __[___ + ("#"=~/$/)]) } __[""=~//]
空白を取り除けば
_="";__=->(___){___<("####"=~/$/)**("####"=~/$/)&& (_<<___;__[___+("#"=~/$/)])};__[""=~//]
わけがわからなくなります。
飽きたのでこの辺で。
追記:
嘘がありました (shinh さんありがとう) 。1.8 でも 1.9 でも $ > < < 65 は "65" が出ます。あと $><<(""<<65) で十分でした。もはや標準出力のために dict を作る意味はないですが、標準入力を ASCII コードに直すのに一応使ってます。s="A" から 65 が欲しいときに dict=~/#{s}/ とかして。こっちもなんかいい方法あるかなあ。
*1:ruby-core:16611 から延々と続く議論とか。
*2:cf. Perl の「記号だけで eval」 。