いろいろ旅行の準備をしたり。
スーツケースを買ってきてみたんだが、着替えをつっこんだら半分くらい空いた。ちょっとでかかったかな。小さすぎるよりはいいか。コートなんかも入れたいしな。
nanocでサイト作成をいじってるんだけど、markdownの文法を覚えてみた。markdownというかkramdownだからちょっと素のmarkdownとは違うのかもしれないけど、けっこういいね。今時RDにこだわってるのもなんだし…というよりもうRDはどうかと思うので全面的にmarkdownに移行することも考えていいかもしれない。
CSSもいじったり。CSSのオフラインで見れるリファレンスが欲しいんだけど見当たらないなー。パラメータの指定方法がわからん。
強調が斜体とか日本語では酷い見た目になるので傍点が使いたいなーと思ったんだけどCSS3にしか無いようだ。まあ最近のブラウザはHTML5とかCSS3をうりにしてるし…と思ったが傍点(圏点とも言うらしい)は全く実装されていなかった。使えねぇ…。3D変形とかアニメーションなんかより余程使うだろうに、テキストすらまともに表示できないのにCSS3とかHTML5だとか騒いでるなんて笑わせてくれる。いや笑えねーんだけどさ…。
とりあえずemもstrongも太字にしておくか。使わねーし。
マルチスレッドアクセス対策を進める。
いろんなスレッドが協調動作するのをなんとかしないとなぁと。いろいろ考えてみたけど結局のところ共有する物はロックして、そうでない物はコピーするってのが基本なんだな。どれが共有でどれがコピーかってのは操作がある物は共有、値を持つだけの物はコピーってのが簡単そう。値を持つだけの物は不変であればコピーする必要すらない。
基本ルールは簡単なんだがコレクションがちょっと困る。ロックしようにもあまり長い間ロックはしたくないが、列挙してる間はロックしてないとまずい。さらにRubyみたいな内部イテレータならともかく、.NETみたいな外部イテレータだと使う側が明示的にロックしないといけなくて忘れそう。コピーしようにも沢山要素を持ったコレクションをいちいちコピーするのはやばそう。コピーする時には浅いコピーなのか深いコピーなのかが問題で、というか決めればいいんだけど、深いコピーを実装するのはちょっとめんどい。あと公開しちゃうと意図しないところで書き換えられちゃったりするのでなんとかしないと。
深いコピーはめんどいとか言わずに必要なら実装すりゃいいだけなので大した問題じゃないな。
イテレータの問題はもうどうしようもない。忘れずロックしろでもいいんだが忘れるし。幸い共有しないといけなさそうでイテレートしたい物っていうのが無かったのでコピーでいいや。読み取り専用なら共有しても問題ないのでそういうところはコピーしなくて済む。
そんなわけで基本ルールに則って、まず値を持つだけのものを不変にしようとしたんだが意外に大変だった。不変にするのはコンストラクタで値を設定してそれっきりにすればいいだけなんだけど、メンバが多いとコンストラクタの引数が多くなって構築がとてもめんどい。ある値をもらってきて一部だけ変えたいなーという時も全メンバ指定しないといけないなんて!
どうすりゃいいんだろうなこれ。メンバが多い奴は分割するとかしないとだめかな。
IronRubyの話。インターフェースへの拡張メソッドをC#で書いてIronRubyからusing_clr_extensionsで使ったんだけど、そのインターフェースを実装してるクラスのインスタンスから呼び出せなかった。いやいやLinqは呼べるじゃねーかとソースを見たらLinqは第一引数がGenericなパラメータなので上手いこと動いてるらしい。第一引数がGenericじゃないインターフェースの場合はレシーバの型がそのインターフェースと一致しないといけないようだ。レシーバがそのインターフェースを実装、じゃなくてレシーバの型がそのインターフェースと一致。インターフェースがインスタンス化できるわけないので常に一致しない。バグっぽいな。短い再現コードを作ったら報告しておこう。
報告するのはいいとして、直るまで時間かかりそうなのでなんとかしないと。ということでusing_clr_extensionsを自前でRubyレベルで実装してみた。ちょっと動作は違うんだけど実用的には足りるだろう。コードは…こんな感じ。
require 'System.Core' def rubyize_name(name) unless /[A-Z]{2,}/=~name then name.gsub(/(?!^)[A-Z]/, '_\&').downcase else nil end end def explicit_extensions(klass) methods = klass.to_clr_type.get_methods(System::Reflection::BindingFlags.public | System::Reflection::BindingFlags.static) methods.each do |method| if not method.get_custom_attributes(System::Runtime::CompilerServices::ExtensionAttribute.to_clr_type, true).empty? then target_type = method.get_parameters[0].parameter_type if target_type.is_interface then type = target_type.to_module else type = target_type.to_class end type.module_eval do [method.name, rubyize_name(method.name)].compact.each do |name| define_method(name) do |*args| klass.__send__(method.name, self, *args) end end end end end end
explicit_extensionsに拡張メソッドを実装してるstaticクラスを渡すと、拡張メソッドをRubyのクラスに追加する。using_clr_extensionsと違ってネームスペースを指定するだけじゃだめなのは手抜き。拡張対象がGeneric型の場合も対応してない。それはusing_clr_extensionsで十分だし。
細かいこと言うとメソッドを追加しちゃうのでC#の拡張メソッドとかusing_clr_extensionsと動作が違うんだけどね。C#の拡張メソッドは元のクラスに同じ名前の物があったらそっち優先。using_clr_extensionsも頑張ってそんな感じにしてる。このexplicit_extensionsでは拡張メソッドで上書き。これは同じ名前にならないように気をつけろってことで、さらに言えばRuby的にはこっちの方がわかりやすい。上書きなんでオーバーロードも中途半端にしか対応できない。結局元のクラスにあるのと同じ名前の拡張メソッドなんか作るなよということで。
一応動作を解説しておくと、public staticでExtensionAttribute属性がついてるメソッドを検索して、その第一引数の型のRuby側クラスまたはモジュールに拡張メソッド本体に移譲するだけのメソッドを追加する、ってだけ。簡単ですね。