@m_seki の

I like ruby tooから引っ越し

末尾呼び出し最適化ごっこ

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門

RubyでつくるRuby」という素晴らしい本があって、toRubyの勉強会で毎月読んでます。そこに出てくるminrubyの話だよ。

RubyでつくるRuby ゼロから学びなおすプログラミング言語入門(紙書籍)www.lambdanote.com



minrubyを改造して末尾呼び出しの最適化ができないか試してみた。

def bar(n)
  foo(n - 1)
end

def foo(n)
  if n > 0
    if n % 1000 == 0
      p n
    end
    bar(n)
  else
    0
  end
end

foo(10000)

カウントダウンを二つの関数をまたぐ再帰呼び出しで実装した例。サブルーチンの末尾(構文木でいう末尾?)がfunc_callだったらそこにマークをつけるようにしたので、ifの分岐先の末尾とかも対象になります。っていうかminrubyにはreturnがないので分岐の先でのfunc_callに対応する必要がありました。


rubyだとこんな感じでスタックが足りません。

$ ruby  loop.rb 
10000
9000
8000
7000
6000
5000
Traceback (most recent call last):
	11913: from loop.rb:27:in `<main>'
	11912: from loop.rb:10:in `foo'
	11911: from loop.rb:2:in `bar'
	11910: from loop.rb:10:in `foo'
	11909: from loop.rb:2:in `bar'
	11908: from loop.rb:10:in `foo'
	11907: from loop.rb:2:in `bar'
	11906: from loop.rb:10:in `foo'
	 ... 11901 levels...
	    4: from loop.rb:10:in `foo'
	    3: from loop.rb:2:in `bar'
	    2: from loop.rb:10:in `foo'
	    1: from loop.rb:2:in `bar'
loop.rb:10:in `foo': stack level too deep (SystemStackError)

改造したminrubyで実行するとこう。

$ ruby interp.rb loop.rb 
10000
9000
8000
7000
6000
5000
4000
3000
2000
1000

なんとか入れ子でも動くようにした。

$ ruby interp.rb interp.rb loop.rb 
10000
9000
8000
7000
6000
5000
4000
3000
2000
1000

追記

末尾にマークつける処理も末尾呼び出し最適化されててかっこよくない?

def mark_tail(tree, genv)
  case tree && tree[0]
  when "func_call"
    mhd = genv[tree[1]]
    if mhd == nil || mhd[0] == "user_defined"
      tree[0] = "tail"
    end
  when "stmts"
    mark_tail(tree[-1], genv) # ここと
  when "if"
    mark_tail(tree[2], genv) # ここは違う
    mark_tail(tree[3], genv) # ここ
  end
end

コードはgistにあるよ

改造したminrubyはこんなの。もっとうまく書けるんだろうなあ。


gist.github.com

テストの合間にプログラミングする

某所から再演依頼をいただいて単独シークレットライブ(re: 反復開発の再演の豪華なやつ)をやりました。とても楽しかったです。

その時に昔よく話してたフレーズを思い出しました。

「テストの合間にプログラミングする」

2009年にも言ってたみたい。というかずっと言ってたけど記録が残ってるのは珍しい。

kakutani.com

 

朝会以後から帰るまでの間、絶えず誰かが製品をテストする。プログラマは全員、毎日決まった時刻に1時間テストをする。さらに一日中テストするテスターがいて、プロの無職もほとんどの時間テストしてる。*1

テストすると問題(本当に問題かどうかはわからない。なにかがおかしい、疑問に感じること)に出会う。そうするとすぐにそばにいるプログラマやテスターやプロの無職がてきとうに集まってきて、なにが起きているのか理解して調査、修正のアクションが決まる。

プログラマの立場で見ると、毎日1時間はテストする帽子をかぶるし、それ以外の時間もだれかのテストで見つかった問題について時間を割くことになるので、(速い人ほど)多くの時間テストに関わっていることになる。んで、その隙をついて今日やろうと思ったチケットについてプログラミングする。計画立てるときもそういうことがあるのを前提にしてるので焦ったりしない。(見積もりにバッファを設ける、というんじゃなく、気兼ねなくリスケする、に近い)

チケット(ストーリー)もすべて製品上でテストすることに紐ついているので、チケットが増える = 毎日のテストが増える ようになる。チケット視点でもテストの合間にプログラミングって感じです。

こういう状況を指して、「テストの合間にプログラミング」って呼んでたわー。

異常な速さ

こんなやり方だとプログラミング進まないじゃん!遅そう!と思うかもしれないけど、実際には異常に速い。疑問に感じてから解消するまでの時間が短いのであらゆるところで待ち時間が短いからかな。プルリク送ってコードレビュー待ちですわーみたいなの、時が止まってるように感じちゃう。いつでも割り込まれるってことは、いつでも割り込めるということなんだよねー。

具体的な問題を前にするとほどほどちょうどいい設計が見つかりやすいのもお得な感じする。前払いでどんな状況でも対応できる設計にしときました!っていうやつもたいていハズれるけど、そいう気持ちのやつは「どんな状況でも対応できるようにしてあるはずだから、その状況がおかしい!現実の方を変えろ!」みたいな問答に時間を費やすので大損する率が高い。前払いしたとしてもどうせいま予見しなかった状況に遭遇するんだよねー、て思ってた方がいい。

速さの話はどうでもいいか。テストの合間にプログラミングするって話でした。

再演

再演はカネで買えます!JPYです!*2

 

*1:そうそう。自分たちにとってテストは「テストする」ものであって書くものではないのだが、ふつうはそうじゃないらしい。もし自動テストによって時間に余裕ができたなら、その時間を手動テストするのに使えるのでお得かもねー

*2:(50kで住民税払うと経費くらいです)

ダメカンダイス作ったよ

 

ダイス作った!

 

ツイートで見かけた、ダメージカウンター用サイコロが便利そうなので真似しました。

 

ポケカのダメージカウンターの代わりにサイコロの目(x 10)を使うのは一般的ですが、50を超えると暗算しづらいのが難点です。そこで6の目のところに100を割り当てることでアクリルのダメカン風に扱えるようにするものです。

 

作戦検討

まず、サイコロ作成の業者さんを探しました。業者さんはいくつかありましたが予算にあわないので自作することにしました。(意外なことにpixivFACTORYでは扱っていないようでした。)

 

結果

f:id:m_seki:20171225221835j:plain

これができたダメカンダイス。手順はこう。

  1. 無地のダイスを用意する
  2. インクジェットプリンタで使えるステッカー用紙を用意する
  3. サイコロ面を印刷してステッカー化する
  4. 切ってサイコロに貼る

 

無地のダイスはAmazonで買いました。海外からの郵便で届いたんですが、2週間以上かかりました。白は杏仁豆腐っぽい白でした。

 

ステッカー用紙はtoRubyのステッカー職人 id:miwa719 の在庫を利用しました。なお印刷、ステッカー化も id:miwa719 がしてくれました。高品質。白地の用紙を使ったけれど、もしかすると透明の用紙の方がいいかもしれません。

  

エーワン 手作りステッカー キレイにはがせる 3セット 28874

エーワン 手作りステッカー キレイにはがせる 3セット 28874

 

 

ダイス面はKeynote, Pages等使って作りました。最初、切り取るのに余裕がある方がいいだろうと思って余白多めにしたんですが、工数は倍になって失敗しました。余白を小さくしたPDFがこちらです。(好きに使っていいですよ)

http://www.druby.org/dice2.pdf

次回があるなら、余白少ないバージョン + きれいなカッター台 + きれいなものさしで作業したいと思いました。