SSブログ

一週間に一回Rubyプログラミングねた7-ブロックとクロージャ [プログラミング]

今までブロックの事を見てきたけど、プログラムの処理手続きがオブジェクトになっているとか、スコープがあって引数とは違い参照渡しだとか、ダラダラと見てきました。

しかし、その仕組みが高階関数や遅延評価にどう役立っているのかとかが全然見えず、使い方がよく分かっていません。ブロックを使っている関数は利用できるけど、実際に作れるかどうかになるとからっきしって所です。色々なライブラリを使っている分には問題ないんですが、ライブラリに手を入れてバグ修正とかは絶対にできないっぽい。

 
実際のソースを眺めて、高階関数や遅延評価をどのように実現しているのかを調べていきます。

高階関数はJavaScriptの時に見たけど、イマイチ利点が分からないと言うか、そういう書き方ができるって事しか分かってない。というか、本を読んだだけなのでほぼ忘れてる。ダメだなぁ。JavaScriptの高階関数の所なら前に見たからすぐに見つかる。はずw。


そもそも高階関数とはなんぞやって事になるね。JavaScriptごときに高階関数が実現できても使う奴いねーよって事がどこかに書いてあった。やっぱり概念的に難しいから、あえて名前を付けて使わなきゃいけないわけだし、言語仕様が絡んでいるから余計ややこしくなる。とはいえ、そういうものの使いどころは知ってしまえば、ある一定の方向性にしか使わない事も多いので、それをケーススタディー的に学ぶだけで大体は大丈夫なはず。

あれ?書いてあると思っていた本に書いてないやw。どこかのWebで読んだ情報と混同しているのかな。
 http://www.h4.dion.ne.jp/~unkai/js/js07.html
再度ググって上の方に来ているサイトを見てみた。前に見たのはここじゃないみたいだけど。
クロージャとは単なる関数ではなく、その関数が定義された環境への参照を持った関数のことです。
ここでいう環境とは変数とスコープです。

Javascriptでは関数はクロージャになります。そして関数と環境は次のような関係があります。

・関数は定義されたときの環境を保持する。
・関数が実行されるときに新たな環境(ローカル環境)が生成される。
・変数へのアクセスが関数の内部で解決されない場合、関数の保持する環境で解決される。

結局、言語仕様でクロージャが使えるかどうかの問題らしい。でも、説明がどうもわかりづらいな。スコープを生成するってことで、新たな環境が生成されるってことね。実質的には、関数発動時の前に中にある変数を保持してて、返り値として戻す時も関数の中の操作が保持されて戻ってくるってことでいいのかな。

結局、関数のオブジェクトが参照型で渡しても戻しても、処理された値が保持されますってことなんだろう。それを一言でクロージャというのでいいと思うが、クロージャをどうやって高階関数や遅延評価に使うのかってことが大事だよね。もしかしたら意識せずにインプリしているかもしれない。




ともあれ、続けてJavaScriptの高階関数を見ていく。Rubyでのクロージャの設定もそれほど違うものではないので(そうじゃなかったら同じClosureという名前を使わないだろうけど)、改めて見るものもないし、C言語的には参照渡ししているだけなので、言語的な難しさというのは特にない。結局、他の言語が特異的に関数内にスコープが形成され、それにクロージャを渡す時もスコープができて保持されるという話だろう。

基本的な所は二点で、関数自体をオブジェクトとできる上、関数に参照渡しで渡せる(JavaScriptの引数であれ、Rubyのブロック付きメソッドであれ)。そのオブジェクトが生きているうちは、その中の変数も保持される。そんな感じ? 細かいことを言うと、別オブジェクトだと中の変数の値も別に保持される、とか。でも、それってオブジェクト指向の話だから、大元が残っていれば値は独立して保持されるわなぁというのが普通。細かい所を言い出すとキリがないから、とりあえずどうやって使えばいいのかを見ていく。


高階関数とは関数(クロージャ)を引数に取る関数、または戻り値として関数(クロージャ)を返す関数のこと

思ったよりも直球な話だね。クロージャを使っていれば高階関数なの? 前はそんな理解だったかすら忘れている。あまりにもあっけない説明だったから覚えていないのか。

主な利用法としては、イテレータなどがあるけど、それは繰り返す操作を後からクロージャで突っ込めるので、特にその処理専用の繰り返しメソッドを作らなくてもいいわけだ。恐らく、繰り返しの部分だけ先に作って、実際にそれぞれにしたい処理を後付で解決するってことかな。前もってデータを選んでおいて、同じように処理するのに特異的なメソッドである、と。


Rubyでもファイルを開けるときには普通ブロック付きメソッドを使う。JavaScriptではどうなのだろう。
 http://www.h4.dion.ne.jp/~unkai/js/js02.html
ん~、これは一般的なライブラリで実装されているものではないよね。ってか、Windowsのライブラリで中身を見るってのはそもそもできないか。

見る限りでは、ファイルを開いたりしてエラー処理しなくちゃならない毎回書かなきゃならない部分と、実際自分がしたい部分とに分けている。エラー処理などの部分は実際の関数側に書いていて、実際にやらせたい部分をクロージャとして後からつっこんでいる。やれていることとしてはイテレータと同じ仕組を使っている。

そう言えば、C++でもSTLとかあったけど、仕事で使わないと分かってからきちんと本を読むのを途中でやめてしまったから、もしかしてそこいらのことを前に学べていたかもしれないね。でもSTLは周りでも使っている人少なかったし、使っても周りの可読性が低くなるだけでいいことがなかっただろうしね。それにちょっと書き方が特徴あった気がして気持ち悪かったし。あとATLという妙に名前が似たMSのライブラリがあったけど、あれはATLの技術を使っていたんだっけ? 今後使うつもりもないので調べたくもないけど。




結局、クロージャの役割の一つは、いつも使う処理は関数側にインプリしておいて、使う時になって自分が作ったクロージャをつっこんで処理を完成させる、ということらしい。少なくとも高階関数の項目で読み取れたのはそういうことだった。JavaScriptだけれども、理念、通念としてはRubyも同じことだろう。

まぁ、書き方からして後から処理を書いて入れているわけだから、薄々分かってはいたものの、実際のコードを見てみないと納得出来ないし、一般的な書き方もできなくなる。同じことをしていても書き方が変態だと誰もコードを読んでくれないばかりか、バグと一緒にせっかく書いたコードが葬り去られることもあるだろう。終わったプロジェクトのことは知る由もないが、変態なコードはバグと絡んでいることが多いので自ら葬ってきたから言うのだが、そういうのはコメントアウト部分か、ソース管理ソフトを使って見てもらうしかないわけだ。こっちも少しの手入れで済めばそうしたいのだが、大体において動けばいいと思っているソースコードがかなり多いのも仕方のない現場も少なくない。

とかく前処理が多い仕組みや、同じお作法が必要なコードにはクロージャを使う意味が出てくるわけだ。オブジェクト指向には処理をポリモーフィズムなどで多様化させる概念があるが、高階関数は処理そのものを束ねて重複処理を無くす意味があり、ソースコードを削減するいい方策であろう。誰もおんなじようなコードを見たくないし、引数が変わるだけで一つづつメソッドを作っていたのではライブラリを使う方はいいにしても、作る方のメンテナンスは面倒なものになってしまうだろう。面倒はそこだけにあるわけではないけど、一つの予想されることはそのくらいかなと今簡単に思いつくぐらいで、高階関数がメリットとなる事は別にたくさんあるかもしれない。


同じ仕組で、
関数同士をつなげちゃう(入れ子にしてるだけですが)、関数合成関数とか、
ハッシュを用いて以前やった処理結果を覚えておいて、処理せず答えを返す、メモ化関数とか、
クロージャがデータを保持する機能を使って、初期化機能付き関数とか、
使い道は色々あるみたいです。Rubyでもできるはず。




そういや、遅延評価ってのはどういうことだ? 何か一番気になっていたんだけど、クロージャがあるJavaScriptにも遅延評価ができるはず。僕にも理解できそうなものがあった。

http://labs.timedia.co.jp/2013/07/write-othello-in-javascript-with-lazy-evaluation.html

ってまたJavaScriptですが、次にRubyで見ていくつもりなので勘弁して下さいw。
うぅScheme前提の話なのでソースから全部読み取らないといけないですな。
愚直に処理させるよりか、処理が軽くなるのはわかったけど、あまりに具体的過ぎてわかりづらい。


ググって先頭に出てくる方を読んでみる。

 http://blog.livedoor.jp/dankogai/archives/50996734.html

小飼弾さんだ〜。言語の中枢の事を探すとちょくちょく見かける。
ええとJavaScriptの三項演算子はC/C++言語と同じでいいんだよな。

http://ja.wikibooks.org/wiki/JavaScript_If%E6%96%87%E3%81%AE%E5%BE%A9%E7%BF%92%E3%81%A8%E3%80%81%E4%B8%89%E9%A0%85%E6%BC%94%E7%AE%97%E5%AD%90

再帰関数について書かれてるね。
先行評価する言語でも、クロージャーが使えるなら、それを利用して評価は後回しにできる。遅延評価(lazy evaluation)したいものを関数でくるんでしまえばいい

うん、クロージャが実行時に評価されるという意味で、遅延評価するってことなんだ。遅延評価とはいうものの、クロージャが実行時に評価しているから後付で評価されるってことだよね。

ふつうの、先行評価型言語では、条件分岐というのは特別な構文で、関数ではない。理由は今まで見て来た通り。片っ端から評価してたらヤバい場合がある

ん~意味合いとしては分かるんだけど、この例はそう書かないと実行できないという実感しかない。今度は単純すぎて実用性が想定しにくいかも。他にも調べてみる。


ん~分かりやすいソースがあんまりないなぁ。というか、Rubyの話なのに今回はJavaScriptのページしか見てないし。名前に偽りありとはこのことだなw。まぁ、別の言語で見たから逆にエッセンスを取り出すのが楽だったと思う。普遍的な考えってのはどの分野においても重要であることが多い。

遅延評価については、Ruby2.0で実装されているものを直接読むかもしれない。とりあえず、遅延評価を残して今回は終わりにしておきます。やっとクロージャのやり方がしっくり来た感じ。後はJavaScriptじゃなくてRubyで実装できるようにならなきゃなぁ。大体、必要な機能はすでにあるんだけどね~。

タグ:Ruby
コメント(0) 
共通テーマ:パソコン・インターネット

コメント 0