やはりRubyでは 〜Scalaの無名関数に憧れて〜

Posted by nanki Wed, 28 Oct 2009 21:18:00 GMT

憧れシリーズ第二弾。

Scalaの勉強でもしようかな、と思ってScalaの本を開いたら、Scalaでは無名関数を_(アンダースコア)を使って定義できるらしい。 Rubyにおけるそれの実現可能性に考えが行ってしまって、Scalaの本を読むのはそれでおしまいになったのだ…

Rubyでも、

[1, 2, 3].map(_ * 2) # => [2, 4, 6]

とか書きたいよね。

Rubyにはもともと、mapなどのブロックを取るメソッドに無名関数(Proc)を引数として渡すことができる

[1, 2, 3].map(&:to_s)

この例では、:to_s は Symbol だが、Proc を返すSymbol#to_proc を定義しておくと、それが呼ばれてよしなにしてくれる。Symbol#to_proc はActiveSupport などで定義されているので、Rails使いにはおなじみだろう。

この仕組みを利用して、 _ に対して送られるメッセージを保存しておいて、to_proc される時に適当なProcを返すような何かを作ればよい。

module Scala
  class LazyCall
    instance_methods.each do |v|
      undef_method(v) unless %w(__id__ __send__).include?(v)
    end

    def initialize(v = nil)
      @v = v
      @msgs = []
    end

    def method_missing(*args, &block)
      @msgs << [args, block] unless args == [:respond_to?, :to_proc]
      self
    end

    def to_proc
      proc do |v|
        @msgs.inject(@v||v) do |r, m|
          r.__send__(*m[0].map{|a|LazyCall === a ? a.to_proc[v] : a}, &m[1])
        end
      end
    end
  end

  def _(v = nil)
    LazyCall.new v
  end

  # _0, _1, ...
  (0...3).each do |i|
    define_method("_#{i}"){LazyCall.new[i]}
  end
end

include Scala

そうしてできたのがこれ。

これを使うと…

%w(a b c d).map(&_ * _.to_i(16))
# => ["aaaaaaaaaa", "bbbbbbbbbbb", "cccccccccccc", "ddddddddddddd"]

[[1, 2], [2, 3]].map(&_0 + _1)
# => [3, 5]

_(Math).sqrt(_0 ** 2 + _1 ** 2).to_proc[[3, 4]]
# => 5.0

& の後に括弧が要ると思っていたがそんなことはなかったぜ。

メッセージをフックする関係上、直接 Math.sqrt(_0 ** 2 + _1 ** 2) とは書けないけど、そう書きたい人はブロック使ってください。

以下簡単な説明

  • _ はLazyCallのインスタンスを返し、受け取ったメッセージを引数と共に保存。
  • [:respond_to?, :to_proc] をはじいているのは、引数として渡される時にこの問い合わせが発生するので、それを無視するため。(副作用として、無名関数では.respond_to? :to_proc が使えなくなると思う)
  • to_proc内で、保存したメッセージを、procの引数に送り直している。
  • 保存された引数の中に、LazyCallのインスタンスがある場合は、call してその結果と置き換えている。これによって、_ * _ のような表記が可能になる。
  • _0は、_.[0]のエイリアス

二夜にわたるProxyObject特集、いかがでしたでしょうか。さて、来週からは…

追記:2009/10/30

Scalaの _ (プレースホルダ構文と呼ぶらしい) は _ + _と書いたとき、二つの _ が同じものを意味しないそうです。

と、閉じた本の続きに書いてあった。

あと、_0 などを使うと、20倍くらい遅い。どうしたものか。

Posted in , ,  | Tags ,  | no comments | no trackbacks

たぶんRubyでは 〜 Maybeモナドに憧れて 〜

Posted by nanki Wed, 28 Oct 2009 05:25:00 GMT

HaskellにはMaybeモナドというのがあって、エラー処理をかなり適当な感じに書けてとても便利そう。

一方、多くのプログラミング言語では、

request.mobile && request.mobile.docomo?

File.open('example.txt').read rescue nil # これはすこし横着

こういった記述を頻繁に使う必要があり、なんとかしたい。

そこで、

class Never
  instance_methods.each do |v|
    undef_method(v) unless %w(__id__ __send__).include?(v)
  end

  def method_missing(*args)
    self
  end

  def end
    nil
  end
end

class Maybe < Never
  def initialize(value)
    @value = value
  end

  def method_missing(*args, &block)
    Maybe.new @value.__send__(*args, &block)
  rescue Exception
    Never.new
  end

  def end
    @value
  end
end

class Object
  def maybe
    Maybe.new(block_given? ? yield : self)
  rescue Exception
    Never.new
  end
end

上のようなコードを実行すると、

request.mobile.maybe.docomo?.end # => true/false or nil

File.maybe.open("file_does_not_exist.txt").read.end # => nil

こういう風に書く事ができるようになって幸せ。 途中のどこかで失敗すると、単にnilが返る。

このように、包んだオブジェクトのフリをするオブジェクトを使う手法はRailsでは多用されていて、読んでみると割と楽しい。ProxyObjectとか呼ぶらしい。

class Maybe < Never のあたりが、

子「お父さん、運動会絶対見に来てね」
父「絶対行くぞ」

一週間後

子「お父さん、運動会絶対見に来てね」
父「たぶん…」

というような会話を想像してしまって、とても人間的。

参考:

Posted in , ,  | Tags ,  | no comments | no trackbacks

選択した文字列もしくは現在のURLをQRコードにするbookmarklet

Posted by nanki Tue, 27 Oct 2009 11:22:00 GMT

選択した文字列もしくは現在のURLをQRコードにするbookmarklet

javascript:s=document.getSelection().toString();s=s.length?s:location;open("http://chart.apis.google.com/chart?cht=qr&chs=200x200&choe=UTF-8&chl="+encodeURIComponent(s),'_blank')

Safariでしか試してない。 Google Chart API 便利。

参考:

Posted in  | Tags  | no comments | no trackbacks