jasagiri @ ウィキ

Functorの紹介

最終更新:

jasagiri

- view
管理者のみ編集可

私は Waves のリクエストラムダマッピングコードを書き直そうと思いました。 どうにか functor に行き着きました。私は Resource クラスで get や put といったメソッドをオーバーロードするのが賢いと思っていたので実際に始めました。私はその考えを捨てましたが (思うに … 再出発すると思います)、オーバーロードされたインタフェースがたくさんの場所にあります。どこで、文字列を渡したら、こうなるかは知ってのとおりです(?);しかしこれはハッシュで、他のことは起こります。通常、入れ子になった if-then や case ステートメントの本当に長い塊で終わることを意味しており、 rambling メソッドや foo_with_hash のような名前のプライベートメソッドを定義します。

Down The Rabbit Hole We Go
Topher Cyll は賢い小さな gem と小さな記事を書きました。もともとは、multi gem をそのまま使う予定でしたが、2,3の小さなことがあり苦しめられました。1つはメソッド定義をオーバーロードするために、クラスに initialize メソッドを定義しないといけませんでした。 (or,本当は, どんなインスタンスメソッドでも, multi を最初に呼び出して使うクラスで再定義されたメソッドを定義したかったのです(?))。私は普通のメソッドのように定義できるよう、2つ目の問題でクラスではなくオブジェクトIDを下に処理するよう、にハックすることに決めました。

私は、すべての入れ子になった状態のために基本的に代理となるメソッドを使うだけの簡単なバリエーションを書くと決めました。私は関数プログラミングで使用される標準的な例である Fibonacci 数列を使うつもりです。 ( Topher を使ったものと一致しないものもあります)。


 fib = Functor.new do
   given( 0 ) { 0 }
   given( 1 ) { 1 }
   given( Integer ) { |n| self.call( n - 1 ) + self.call( n - 2 ) }
 end

これはかなり自己説明的です。基本的にこういっています。「 0 を与えると、0 を返す; 1 を与えると、 1 を返す; Integer を与えると block の値を返す。」実際にまるでそれが Proc であるかのように呼ぶことができます。:

 fib.call( 7 ) # => 13

事実、あなたは全体の系列を生成するために to_proc と handy & operator を使うことができます。:

 [ *0..10 ].map( &fib )  # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

かなり格好よいです。



A Veritable Bounty Of Patterns
引数は定義に基づいてマッチしていることに気をつけてください。:最初に 0, それから 1, 最後には Integer に対して。これは「本当の」パターンベースディスパッチほど洗練されていませんが、より速くてより予測可能である利点があります。

マッチングははじめに == を使い、次に === を使い、(case equality, which will work for classes and regexps, among other things), 最後に call を使います。(もしオブジェクトがメソッドに応じるなら)。これはあなたに「guards」という賢いマッチャーを入れることを許し、任意の洗練されたパターン・マッチングを実装することができます。

テーブル行のに別の色を使って明確にフォーマットする必要があるような古典的なケースを扱います。:

 stripe ||= Functor.new do
   given( lambda { |x| x % 2 == 0 } ) { 'white' }
   given( lambda { |x| x % 2 == 1 } ) { 'silver' }
 end

# ... sometime later ...
rows.each_with_index { |row,i| tr :style => "color: #{stripe.call(i)}" }

Granted, これは特定の要件については過剰ですが、うまくいけば lambda guards を使用する力を例証できます。私たちはこのようにもフィボナッチファンクタを実行できるでしょう。:

 fib = Functor.new do
   given( 0 ) { 0 }
   given( 1 ) { 1 }
   given( lambda { |n| n > 1 } ) { |n| self.call( n - 1 ) + self.call( n - 2 ) }
 end

どちらが技術的に正しいでしょうか。負の値により ArgumentError を受けるでしょう(?) (これは何とも一致しない場合はいつでも起こることです)。

Functor Gets Some Class
これらすべてはすばらしく、かなり満足しています (実際、上で行ったことすべてがもともとあったというわけではありません。 Topher と Lawrence Pit は後から独自に lambda guards を実装しました。)。 私は Waves mapping code をハックするのに戻ろうとしました。 しかし頭の後ろで、オブジェクト指向の影がちらついていました。これはどれくらい困難だろうと自問しまいした、クラスに functors と名付けたハッシュを追加するだけだろうか? 有名な締めくくりの言葉、もちろん、頭の声を静めることはできず、ある晴れた日曜日の朝、私は負けました。

 class View
   include Functor::Method
   def initialize( response ) ; @response = response ; end
   functor( :render, String ) { |s| @response.write(s) }
   functor( :render, Proc ) { |p| render( p.call ) }
   functor( :render, Object ) { |k| render( k.to_s ) }
   functor( :render, Object, Hash ) { |k,h| @response.headers.merge!( h ); render(k) }
 end

ここに想像上の View クラスのための簡単な制御の逆転(IoC)のシナリオがあります。私たちはいろいろな異なった型のオブジェクトを扱う render メソッドをオーバーロードしました。それらは簡単な文字列の入力で terminal condition に再帰的にすべて実装されました。ヘッダに書くために任意のハッシュも加えました。私たちはステータスコード(例では、Exception をオーバーロードし過ぎる)や他のいろいろなバリエーションを扱うこのアプローチを容易に拡張できました。

Functor::Method モジュールは、はじめに2つのクラスを定義します。: functor と functors です。これはすでに例で見ているように (a) functor メソッドを作成します;そして (b) 直接名前を与えられたクラスへ functors からアクセスします。舞台裏では、 each class that calls functor を呼んだそれぞれのクラスがそのクラスで定義された functor を追跡するためにインスタンス変数 @__functors を得るでしょう。 functor メソッド自体は functor という名前でメソッドを定義します。 このように、

 view = View.new( response )
 view.respond_to? :render # returns true

このメソッドは実際に同じ名前の functor に対して dispatch するでしょう。それはメソッド呼び出しの特別なバリエーションを使用します。だから、あなたが望めば、こんな事も可能です。:

 # these two lines do more or less the same thing ...
 view.render( "hello" )
 View.functors[:render].apply( view, "hello" )

大きな違いは render メソッドが実際何にもマッチしなかった場合、 super を呼ぶことだけで、この場合、render は既定クラスで実装されます。もし1つもマッチしなかった場合は (either in the derived or base class) ArgumentError が raised されるでしょう。だから普通の使い方を望まない場合は2番目のテクニックを使います。

それを使いたい1つのケースとして、 however, functor の lambda から既定クラスの super を呼び出したいときがあります。実際にはそれができないので( lambda は実際はメソッドから呼ばれ、super の get を confusedされるため、ちょっと長い話になります)、可能なベストは方法は既定クラスの functor を明確に呼ぶことです。

# functor equivalent of super - a bit less elegant!
self.superclass.functors[ :foo ].apply( self, "bar" ) 

私はこのメソッドをちょっとでもきれいにするアイデアを加えました(?) say, super_fun, や less whimsically, functor_delegate ?(?) しかし、クラスにそんなに頻繁に使わない別のメソッドを追加するということは本当にそれらを助けるようには思えません。別のアプローチでは継続を使うことですが、私はブロックに明確な方法を示すことはできませんでした。

幸いなことに継承がとても合理的にサポートされており、 super を別にすれば、あなたはクラスを再定義したり再オープンすることさえ可能なのです (忘れないでください、そうすると、あなたの再定義はマッチするまで行の最後まで実行されます。)。一般に Functors はそのように振舞います。

スタンドアロンの Functor を使用することと異なっていることは (一般にメソッドとして Functor を使用して) 自分を束縛することを許すことが指摘されます。 Fibonacci の例で, self referred back to the Functor 自身を返しました。 When using apply, however, それはメソッド呼び出しされたクラスを指します。

Odds And Ends
このプロジェクトは RubyForge にあがっており、多分よりsignificantly, GitHub に関して, ソースをいじったりパッチを送りたいとい思ったときには...遠慮なく質問やコメントを送ってください。

With this foundation, もっとできることがあるでしょうか? 特に, functor を与えたり, curry 化したり, 構成したり, etc.? 私はこれらのことをどれも加えていません。素の Ruby で行うことが比較的簡単な間は、まだ便利であるかもしれません。(?) 私は場合によっては他のライブラリと衝突するかもしれないで Proc にメソッドを加えるのを避けていました。 しかしながら Functor では, 好きなことは何でも出来る可能性があります。

私は Ruby コミュニティから、この小さいライブラリを使用するクールな方法があるとかまだ私が考えてもいない機能を追加したとかという連絡があるのを楽しみにしています。実際、むしろそうあって欲しいと願っています。私は Waves にこれを使っており、おそらく、他のブログエントリーを正当化できる発見できるでしょう(?)。
記事メニュー
目安箱バナー