トップ 追記 RSS feed

継続にっき

2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|
2014|05|09|
2015|12|

2015-12-12 (Sat)

))) TRICK 2015で入賞した

Ruby版IOCCCであるところのTRICK 2015というコンテストで審査員賞"Matz Lisp award"をいただきました。

受賞コードを抜粋するとこんな感じ。

(DEFINE (FACT N) . (
  (IF (EQ? N . (1)) . (
      1 [* N . ((FACT (- N . (1))))]))))

(DISPLAY (FACT 6))

よく訓練されたRubyistにとってはただのRubyのコードにしか見えないかもしれませんが、 一応これはSchemeとしても有効なコードになっていて、実行すると6!を計算して出力します。 また、任意のプログラムを外部から与えることができるようにもしていて、 その一例としてSICPに出てくる超循環評価機をポーティングしています(metacircular.rb)。

ということで、今回の作品は一言でいうと「PolyglotなSchemeサブセットのインタプリタ」でした。

Schemeにおいて、シンボルが大文字小文字を区別しないのはR5RSまで、[]を()と同様に扱うようになったのはR6RSからなので より正確には「R5RSサブセット+一部R6RS拡張」になります。 シンボルを大文字にしたのはなるべくオリジナルに忠実に超循環評価機のポーティングをしようとしたため*1ですが、 原理的には小文字にすることも可能です。純粋なR6RSサブセットをお求めの方のために、小文字バージョンも用意しておきました(entry-lowercase.rb)。

実装のポイントはobj.callのシンタックスシュガーobj.()の活用です。remarksから引用します。

リスト((A) B)はこのままだとRubyでsyntax errorになりますが、 ドット対表記((A) . (B))にすると((A).call(B))と解釈されパーサを通るようになります。

これを利用してRuby/Schemeの両方で有効なコードを用意し、 実行時にmethod_missing/const_missingなどを使いながら構文木を組み立ててSchemeプログラムとして評価しています。

基本的にはこれですべてですが、Schemeにおける`FOO'と`(FOO)'はRubyのコードとしてパースすると違いがないので、後者は(FOO . ()))と書くことにしようといったルールを決めていかないといけないのがちょっと大変でした。

また、Refinementsも使っていて(metacircular.rb#L7)、RubyコードとSchemeコードが無理なく共存できるようになっています。ただ、Object.const_missingはRefinementsが有効にならないという仕様があり*2、これだけはグローバルに影響が出てしまうのが心残りです。

追記(2015/12/23)

実はここまで書いたその日の夜にmethod_missingでRefinementsが有効になるのはバグだとの判断がありプログラムが動かないようになっていました*3。 確かにRefinementsの仕様をよく見てみるとIndirect method accessesではRefinementsを有効化しないという記載があるためこの変更には納得するところで、図らずもTRICKの理念の一つである「Rubyの仕様を安定化する」ことに貢献できた感があります。

なお、GitHub上のコードはRefinementsを使わずにモンキーパッチをあてるように修正したので、少なくとも当面は動いてくれるはずです。

*1 小文字だとifなどがRubyの文法と衝突してしまう

*2 Bug #11655: Can't refine Object.const_missing

*3 Bug #11809: method_missing should not be refined


2014-09-20 (Sat)

))) RubyKaigi 2014 まとめ

「Power Assert in Ruby」というタイトルで発表してきました。まさかRubyKaigiの壇上に立つ日が来るとは。光栄な話です。

以下、振り返り。

  • RubyKaigi当日まで
    • CFPの締め切り1週間ぐらい前にpower_assertがtest-unitに組み込まれることになったので勢いでsubmitボタンを押す。
    • 準備しだすも誰得な話にしかならないような気がしてきて延々とスライドの構成に悩む。 *1
    • Rebuild.fmのa_matsudaさん回を聞いたことで、追い打ちのようにハードルが3段階ぐらい上がる。
  • 1日目
    • nagachikaさん、ささださん、nariさんと冒頭から連続3セッションで名前を出してもらったことで謎の達成感を得る。
    • Hall-Aが期待を裏切らない濃さで満足。
    • Ruby committers vs the Worldでマイクが回ってくる。パターンマッチ入れたいですよね。
  • 2日目
    • MatzのキーノートはRuby 3.0の話。あれ、パターンマッチは…?
    • Power Assert in Ruby
      • 会場の反応がよくてスピーカーとしては大変やりやすかったです。来ていただいた方、ありがとうございました。
      • 「TracePoint使っている人」と聞いて手が上がったのは10名強というところ。このセッションをきっかけに利用者が増えるとうれしいです。
      • akrさんのメソッド再定義すればいいんじゃないかという指摘は目から鱗だった。なるほどなあ。
        • アトミックに出来るかどうかがまずはポイントかな。
        • 影響範囲を限定するためにRefinementsと組み合わせるということを考えてみたけどこれは微妙?
      • コアに欲しい機能は、ということでいくつか。
        • TracePoint経由で位置情報を取得。
        • method_missingが呼ばれたときにオリジナルのメソッドのidを取得する手段。
        • JavaScriptのarguments相当。
          • これがあると差分比較まで行える。
        • Procのソースコードを取得する手段。
          • PowerPをgemにしていないのはこれが理由で、ruby -e 'p { foo }'などとされると対応しようがない。
    • 夜は#unagiawardへ。yharaさん曰く「神々の集い」。鰻が運ばれてくるまでの間に2.0.0-p576/2.1.3がリリースされている辺りさすがというほかない。
    • #unagiaward解散後にMatz達と合流することになったものの風邪気味だった体調が悪化してきたので退散。その後も面白い話があったようで残念すぎる。
  • 3日目
    • 午後からふらーっと行ってコードを書いたり。

最後に。今回、スピーカーということでスタッフの方の活動を目にする機会が多かったのですが精力的な仕事ぶりに圧倒されました。スタッフの皆さん、本当にありがとうございました。

*1 悩むだけ悩んで結局当初の構成ほぼそのままだったんですが、それなりに好評だったようで安心しました。


2014-05-31 (Sat)

))) test-unit-power_assertをリリース

Groovyなどいくつかの言語のテストフレームワークには、テスト失敗時にコードの各式の値を出力するPower Assertと呼ばれる機能があります。 今回、Ruby 2.0以上 + test-unit向けに同様のものを実装してみました(test-unit-power_assert)。

利用例は以下の通り。

require 'test/unit'
require 'test/unit/power_assert'
class MyTest < Test::Unit::TestCase
  def test_failed
    power_assert do
      "0".class == "3".to_i.times.map {|i| i + 1 }.class
    end
  end
end

power_assertメソッドにテスト対象の式をブロックとして与えるだけですが、出来れば-rオプションを使うなどしてテストコードが読み込まれる前にこのライブラリをrequireするようにしておくと情報の取りこぼしを防げます。

# 事前requireなし

        "0".class == "3".to_i.times.map {|i| i + 1 }.class
            |            |    |     |                |
            |            |    |     |                Array
            |            |    |     [1, 2, 3]
            |            |    #<Enumerator: 3:times>
            |            3
            String

# 事前requireあり

        "0".class == "3".to_i.times.map {|i| i + 1 }.class
            |     |      |    |     |                |
            |     |      |    |     |                Array
            |     |      |    |     [1, 2, 3]
            |     |      |    #<Enumerator: 3:times>
            |     |      3
            |     false
            String

追記(2014/08/04)

test-unitにPower Assertが標準機能として取り込まれassertメソッドにブロックを渡せるようになりました ([ruby-list:49902] [ANN] test-unit 3.0.0)。 これを受けてtest-unit-power_assertもバージョンアップを行い、同様の書き方ができるようにしています。


2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|
2014|05|09|
2015|12|
トップ 追記 RSS feed