クロージャの有用な利用法について

このQ&Aのポイント
  • クロージャを使用するメリットについて、グローバル変数の数を減らし、関数内の変数値を保持できることが挙げられます。
  • さらに、1回しか利用しない関数に名前を付けずに済むという利点や、コードの行き先が迷わず分かりやすくなるという利点もあります。
  • オブジェクト指向と関数型プログラミングを考慮すれば、1回しか使わない処理を関数化する必要性は薄いですが、モダンでカッコいいコードを書くためにはクロージャを利用すると良いでしょう。
回答を見る
  • ベストアンサー

クロージャを利用するメリットについて

コボラーです。 いや、PHP、Perl、VB、C#、JavaScript、VC++、ActionScript、Java、Objective-Cは書けるけどCOBOLは書けないのでコボラーではありませんね。 モダンなプログラムコードが好きではない、レガシーなプログラマーとしておきましょう。 自己紹介はこれくらいにして・・・ 今までもアチコチで話されていると思いますが、クロージャ(無名関数)を利用するメリットについてです。 ・グローバル変数の数を減らせる ・関数内の変数値を保持しておける この2つのメリットについては理解しました(これがメリットかどうかは別として) その他には ・1回しか利用しない関数に名前を付けなくて済む ・1回しか利用しない関数が、利用する箇所に書かれているので、コードを追っている間に迷子になり辛い こんな意見も見受けられました。 もしかしたらこれは、オブジェクト指向 vs 関数型プログラミングという話しになってしまうかもしれませんが、そもそも1回しか利用しない処理を関数化するメリットってなんでしたっけ? オブジェクト指向で考えれば、1メソッド当たりのコード数は少ない方が良いとされていますので、そもそも無名関数ではなくオブジェクティブな作りにすると思います。 関数型プログラムでは、main処理の中で複数処理するような共通処理部分を関数化し、整理をしていきますよね。 そうなると、どちらにしても「1回しか使わない処理を、そもそも関数化する必要性」が見いだせないのです。 それなのにその部分を無名関数にする必要性は感じられませんでした。 もちろんコールバックに便利とか、イベントリスナーに使うのが便利なのは理解していますし、利用しています。 もし・・・ 利用する理由が「モダンぽくてカッコいいから」というなら、それならそれで構いませんし、それについて自分の意見をぶつけるつもりもありません。 (自分のコードがレガシーでかっこ悪くて無駄だと言われたら、全力で応戦しますが・・・) ぜひ明日から私もついつい使いたくなってしまうようなクロージャの有用な利用法を私に教えて下さい。

質問者が選んだベストアンサー

  • ベストアンサー
回答No.1

うーん、まぁ、「使いたくない」ってぇのなら使わなくても構わないとは思いますが・・・。 ちょっとLisp/Schemeを使って説明してみますか。 例えばくだらない問題ですが、「数値のリストを与えられた時に全部の数値に1を足す」と言う処理を考えます。当然、これを関数として書く、って話になりますが。 もちろん、手続き型言語のように「ループで回して」書くことも可能ですが。 大体、関数型プログラミングの思考だと、マッピング処理を考えますね。次のようにして関数を定義します。 (define (add1-to-list ls)  (map (lambda (x) (+ 1 x)) ls)) もちろんクロージャ(あるいは無名関数/ラムダ式)を使わずに別に関数を定義して内部で使っても構わないんですが・・・なんかメンド臭いですよね(笑)。 要するに「よっぽどメンド臭い式を定義する必要がない場合」、どういう作業をマッピングで指示するか、とか言う場合、内部的にクロージャを埋め込んで書いた方が「スッキリする」と言う例です。 と言うわけで、一番多いケースは「マッピングでどういう作業をさせるか」簡単に記述する際にクロージャを用います。 第二の使い方は「返り値が関数である関数を返す」場合です。 通常、と言うか古い言語の場合、関数が返す値は数値であるとか文字列のようなデータ型ですが、一般にクロージャ自体もデータとして扱えます。 例えば「与えられた数値nに対して別の数値xを足して返すような関数を返す関数を書け」・・・・日本語で書くとメンド臭いですが(笑)、こう言うものをクロージャを使って定義する事が可能です。 (define (add-n-function n)  (lambda (x) (+ n x))) これは実行してみると例えば、 > (add-n-function 1) #<procedure> > となって、「関数が返る」わけですよね。 まあ、このままじゃしょーがないんで、次のように「与えられた数に1を足して返す」関数をadd-n-functionを使って定義してみます。 (define (add-1-function x) ((add-n-function 1) x)) そうすれば、 > (add-1-function 1) 2 となって1に1を足した数値を返してくれます。 馬鹿馬鹿しい例なんですが、一方、そうすると「汎用」として「xを足すクロージャを作った」と言う事はこの関数は再利用が可能だ、と言う事ですよね。add-2-functionとかadd-3-functionとか全部「クロージャを返す関数」を利用して定義する事が可能だ、と言う事です。 つまり、無名関数を返すように設計する事も、コードの再利用可を促進出来る、一つの方法だ、と言う事です。

massa_si
質問者

お礼

詳しくご説明ありがとうございます! > うーん、まぁ、「使いたくない」ってぇのなら使わなくても構わないとは思いますが・・・。 > つまり、無名関数を返すように設計する事も、コードの再利用可を促進出来る、一つの方法だ、と言う事です。 今までにもプログラミング言語を効率化するために色々な手法が取られてきましたが、クロージャもやはりその一環と捉えるべきだと感じました。 今まで通り「必要に応じて必要な技術と手法を利用」して、ユーザの望むものを最短の工期で、最小の不具合に抑えて提供する事を忘れずに構築していきたいと思います。

その他の回答 (1)

回答No.2

> 今までにもプログラミング言語を効率化するために色々な手法が取られてきましたが、クロージャもやはりその一環と捉えるべきだと感じました。 そう捉えていただいて幸いです。 まあ、他の関数型言語とかそんなに詳しくないんですが、特にLispに関して言うと、クロージャは「色々実験するのに便利だ」って事があるんです。 あくまで「理論的な話」ですが。 オブジェクト指向 vs. 関数型プログラミングってのは理論上は無いんですよね。と言うのも、クロージャを持った関数型プログラミングの発想だと、原理的にはオブジェクト指向ってのは「クロージャを使った実装」だからなんです。 これは実際にプログラミング言語を実装する際にクロージャを使う、って意味ではないんですが、モデルとして考えるとクロージャを使ってOOPを実装する、ってのは説明が容易なんですね。 何故ならクロージャは環境を保持するから、です。 例えば次のページ: 15分でわかる かんたんオブジェクト指向: http://qiita.com/koher/items/6878c80014992900add7 での簡単なクラスの定義の例ですと、クロージャでこのように実装出来ます。 (define (Person givenName familyName age)  (lambda (message)   (case message    ((getGivenName) (lambda () givenName))    ((setGivenName) (lambda (newGivenName)             (set! givenName newGivenName)             givenName))    ((getFamilyName) (lambda () familyName))    ((setFamilyName) (lambda (newFamilyName)             (set! familyName newFamilyName)             familyName))    ((getAge) (lambda () age))    ((setAge) (lambda (newAge)          (set! age newAge)          age))))) まあ、これは破壊的変更を使ってるんで純粋関数型プログラミングじゃないですし、また「継承」も出来ないですけど、一応「クラス」と「メソッド」と言う単純なモデルは「クロージャで実装出来る」と言う事を示しています。 > (define person (Person "Albert" "Einstein" 26)) # 新しいインスタンスを定義 > ((person 'getGivenName)) # givenNameを返すメソッド "Albert" > ((person 'getFamilyName)) # familyNameを返すメソッド "Einstein" > ((person 'getAge)) # ageを返すメソッド 26 > ((person 'setAge) 27) # ageを変更するメソッド 27 > ((person 'getAge)) # personのageは変更されている 27 このように「メッセージをインスタンスに送って」返すと言うモデルはクロージャで実装出来ますし、OOPのように「別のインスタンスを作っても」干渉し合う事はありません。 もちろんこれは「理論的なモデル」なんで、こんなに無名関数を二段階にしてまで書いて、ってのは実用上は面倒くさいし馬鹿馬鹿しいんですが、一方「プログラミング言語作成実験」等ではクロージャは大変威力を発揮します。 同様に、最近流行りの「遅延評価」と言う機構も、実はクロージャでモデルが作成可能なんですよね。 一応Lispでの基本関数、cons、car、cdrを説明しておきますが、(cons a b)とは(a . b)と言う対(ペア)を作り上げる関数です。car はペアから aを取り出す関数、cdrはペアからbを取り出す関数です。 この三つの基本関数と無名関数を使って(a . (lambda () b))と言うペアをでっち上げるのが「遅延評価」の基本です。と言うのも無名関数/クロージャは関数なんで「評価しろ」と言わない限り状態を保持したままで値を返しません。つまりこれが「遅延評価になる」と言うわけですね。 これらを使って取り敢えず次の三つの基礎関数を作ります。 ;;; 遅延評価リストsのn番目を取り出す関数 (define (lazy-ref s n)  (if (zero? n)    (car s)    (lazy-ref ((cdr s)) (- n 1)))) ;;; 遅延評価リストへのマッピング関数(中にクロージャ有り) (define (lazy-map proc . argstreams)  (if (null? (car argstreams))    '()    (cons     (apply proc (map car argstreams))     (lambda ()      (apply lazy-map         (cons proc (map (lambda (x) ((cdr x))) argstreams))))))) ;;; 二つの遅延評価リストを足し合わせる関数 (define (lazy-add s1 s2)  (lazy-map + s1 s2)) そして、クロージャを利用して、次のような「データ」を書いてみます。 ;;; フィボナッチ数列 (define fibs  (cons 0     (lambda () (cons 1             (lambda () (lazy-add ((cdr fibs)) fibs)))))) 上のfibsは中にクロージャをツッコんでいますが、一方引数が存在しないので、実はこれは「データ列」です。しかも事実上、「フィボナッチ数列」の無限長の数列を表しています。マジです。 良く見ると再帰的定義になってるのがおわかりでしょう。フツーならこのような「終了条件が明示されていない」ブツを定義して実行した途端、暴走してメモリを食い尽くすのが常なんですが、生憎、「クロージャを利用して」計算を止めてるんで、そういう暴走の心配がありません。でさっき書いた「lazy-ref」で任意の番号の数値を取り出せるようになっています。 > (lazy-ref fibs 0) 0 > (lazy-ref fibs 1) 1 > (lazy-ref fibs 2) 1 > (lazy-ref fibs 3) 2 > (lazy-ref fibs 4) 3 > (lazy-ref fibs 5) 5 > (lazy-ref fibs 6) 8 このまま続いていきますが、「理論的には」このfibsと言うデータは「無限長」なんで、どんな添字番号の数値も取り出す事が出来ます。 これも「クロージャの」効果ですね。 まあ、この辺はちと理屈っぽい(っつーか殆ど机上の空論・笑)なんですが、一方、LispなんかではOOPを実装した言語より「自力でOOPを組み上げられる」パーツとしてのクロージャ、と言うのが愛されていますね。 それをしろ、とは全然言いませんが(笑)、「ベーシックで強力な」基本機能としてのクロージャ、ってのが関数型界隈でのポジションになるでしょうか。

massa_si
質問者

お礼

関数型はあまり詳しくないのですが、 オブジェクト指向ってのは「クロージャを使った実装」 クロージャを使ってOOPを実装する事ができる。 この2つについては感覚的に理解できます。 私がいつも利用しているプログラム言語に置き換えて、もう一度効率的な利用方法を考えてみたいと思います。 ありがとうございました。

関連するQ&A

  • クロージャは、再帰処理ではないのでしょうか?

    クロージャの日本語訳は、再帰処理だと思っていたのですが、違うのでしょうか? 内側の関数から、外側の関数の変数を参照できればクロージャで、必ずしも再帰でなくてもよい?

  • オブジェクト指向の本当の便利な点。

    オブジェクト指向の特徴は、ある程度分かりました。 が、個人で小さなソフトを作り、できるだけ自分でプログラムを作りたいため、オブジェクト指向の利点が今ひとつ分かりません。 PHPでは、構造化でできるだけグローバル変数を減らし、関数内でも関数名+変数名という変数名にしていたので、変数の名前が重複すると言ったこともなかったし。 関数名+でない場合も、関数内では不必要な変数は値を解放していたし。 過去の資産も関数を再利用する事もよくありましたし、継承のような事もしていました。 オブジェクト指向の便利さは分かるのですが、どうも実感できないというか、その便利さを持て余しているというか。 構造化プログラミングでも、さほど問題ないし。 delphiなので、JAVAのようにオブジェクト指向(クラス)が必須という訳でもないし。 逆にクラスを作ってしまうと、メモリーから解放しないといけないので、それが少し怖いです。 で、オブジェクト指向の利点をあげるとしたら何ですか? 可能なら、上位から3つくらいを詳しく書いてください。 ソフトは大規模か小規模か、制作者は大勢か少数・個人か、それは構造化プログラミングでは無理な事なのか? オブジェクト指向の利点や特徴は、分かるのですがピンとこないというか、実感できないというか・・・。

  • phpでオブジェクト指向と呼べる設計をするには・・・

    phpで、なるべく同じ処理は関数化し、 ひとつの大きな処理としてまとめています。 これは、オブジェクト指向といえるのでしょうか? オブジェクト指向とはもっと高度で汎用性が高く たんなる関数化ではオブジェクト指向とはいえないでしょうか? 綺麗なオブジェクト指向とは一体のどようなものなのでしょうか? どこまでオブジェクト化すれば オブジェクト指向なのかよく分かりません。 使いまわしがきけばオブジェクト指向なのでしょうか? 他のプログラムに影響を与えなければオブジェクト指向なのでしょうか? 質問が抽象的ですが、よろしくお願いします。

    • ベストアンサー
    • PHP
  • phpのオブジェクト指向でつまづきました・・・

    phpのプログラムをオブジェクト指向で書こうと思ったのですが、クラス間のデータのやり取りのあたりでつまづきました。 クラスはそれぞれ 1.設定用 2.データの読み書き用 3.入出力用 4.データ処理用 5.メイン です。 オブジェクト指向で書く前よりはコードも見やすくなったのですが、例えば4のデータ処理のところからは1、2、3の全てのプロパティを参照していたりして、なんだか複雑に。 オブジェクト指向で検索するとwikipediaには プログラムを構成するコードとデータのうちコードについては手続きや関数といった仕組みを基礎に整理され、その構成単位をブラックボックス とすることで再利用性を向上し、部品化を推進する仕組みが提唱され構造化プログラミング (structured programming) として1967年にエドガー・ダイクストラ (Edsger Wybe Dijkstra) らによってまとめあげられた と書いてありました。これだと関数を種類ごとにまとめただけであまり部品化はされておらず、前と変わらないような気がしてしまって・・・ 例えば設定用のクラスはのプロパティほとんどすべての所で値が必要になるのですが、毎回newを使うのは気が引けるので、クラスの外で new でインスタンス化して必要なところから毎回 global で呼び出したりしているんですが・・・使い方が間違っている気がするのですが、そういったことを解説しているサイトが見当たらなかったため、全く分からない状態です。そもそもこの場合、設定用の値はクラスにまとめるべきなのかどうか・・・。 書く時は$クラスー>メンバ変数 とか $クラスー>メンバ関数 のように書くので今何をしているのかが分かりやすく、それは便利だと思うのですが。 すみません。自分でも上手く説明できずによくわからない文章になってしまいましたが、結局の所オブジェクトがどんな物でどんな書き方をすれば良いかが分かっていないのだと思います。そういった所を分かりやすく教えてください。お願いします。

    • ベストアンサー
    • PHP
  • オブジェクト指向はどう利用するのですか?

    オブジェクト指向はどう利用するのですか? JavaScript初心者です。 初心者から中級者になるには、このオブジェクト指向をマスターする必要があるのですが、(個人の見解です...)オブジェクト指向がさっぱりです。 まず、オブジェクトは数を代入するもので、 オブジェクト名.変数名 = 変数値; alert(↑) で、変数値が表示されることはわかりました。 しかし、利用方法が分かりません。 オブジェクトに代入しなくても、 num_aaa=123; num_bbb=456; や、 num[1] =123 ; num[2]=456; と配列を使うのもいいと思います。 オブジェクトでは、何ができるのでしょうか??

  • マルチプルインスタンスのメリット

    100%の自信をもって回答できる方のみ回答をお願いします。 Java等のオブジェクト指向プログラミング技術において、クラスからインスタンスを たくさん作れるというのが特徴の一つとなっていますが、クラスからインスタンスを たくさんつくれるメリットはなんでしょうか。 クラスからインスタンスをたくさん作れなくても、1つ作れば動くプログラムはたくさんありますし、 そういうプログラムであれば、staticなアクセスにすればよいですよね。 クラスからインスタンスがつくれることを説明した本やサイトは山ほどありますが、 インスタンスがたくさん作れることのメリットを説明した本をみたことがなく、 「なぜオブジェクト指向でつくるのか」という本を買って読みましたが、 納得がいくメリットを感じられませんでした。 また世の中のソースをみると、みんな、すぐにクラスを作ってオブジェクトを作っていますが、 みなさんがどうして、オブジェクトを作らなくてもコーディングできるケースのプログラムを オブジェクトを作るのか不思議でしかたありません。 どうしてオブジェクトを作らなくてもいいケースでもオブジェクトを作るのか、 またたくさんオブジェクトを作れるメリットはなんなのか、教えてください。

  • コードの簡素化とメリットについて

    例えば以下のようにコードを簡素化すると、当然書くほうの管理が楽になりますが、処理速度が速くなったりあるいは、プログラムサイズが小さくなる等のメリットがあるのでしょうか? <冗長なコード> 関数1{  関数3の定義 } 関数2{  関数3の定義 } <簡素なコード> 関数1{  関数3の呼び出し } 関数2{  関数3の呼び出し } 関数3{ }

  • JavaScriptの関数オブジェクト

    JavaScriptの匿名関数から関数オブジェクトが作られるのはいつでしょうか。 ※ブラウザの実装によるかもしれませんが・・・ Callオブジェクトがその関数の実行時に毎回生成されるので、 処理コスト的に考えれば、関数オブジェクトはコンパイル時の1回だけで済むはずですよね? 関数コード自体はどんな呼び方しても共通なはずですから、 単に実行時に適切なCallオブジェクトをくっつけてあげるだけでいいはず・・・ (Functionコンストラクタ使うなら別でしょうけど) でも、クロージャについて「その都度、関数オブジェクトが生成されるのでメモリ効率が悪い」 という意見を割とあちこちのブログとかで見ます。 Callオブジェクトにでかいローカル変数が含まれると無駄、ってのなら分かるんですが。 このあたり、ECMAScriptの仕様では決められていない部分なのでしょうか。

  • 無名関数を使うメリットは何ですか?

    Javascriptに限らず、多くのプログラム言語で無名関数が使えると思います。以下の2つの記述法は、挙動に違いは全くありませんよね? function func() { return "普通の関数です。"; } var func = function() { return "無名関数です。"; } なぜ無名関数を使うのか調べてみたところ、「最近Ajaxが流行ってきてライブラリを使う機会が増えてきた。それらを利用する際、関数の名前が重複して誤作動するのを防げる」という答えを見つけました。しかし試してみたところ var func = function() { return "無名関数1です。"; } var func = function() { return "無名関数2です。"; } alert(func()); のように名前が被った場合、従来と同じで後に書かれた関数が動作します。イベント駆動型関数を無名関数にするメリットは分かります。たとえば window.onload = function() { alert("ウィンドウの読み込みが完了しました。"); } と書けば、他の関数との衝突によってこの関数が動作しないことはありません(ライブラリを使ってwindow.onloadが重複してしまったら別問題ですが)。 結局のところ、無名関数を使うメリットは何なのでしょうか?

  • オブジェクト指向コンパイラのインスタンスの構造について

    インタプリタやコンパイラの中身について興味があり勉強しています。といってもまだ簡単な字句解析や構文解析のレベルです。 読んだ本にはオブジェクト指向言語については書かれていませんでした、そこで一つ疑問に思ったこと点について質問させてください。 簡単な言語の場合、使用する変数が宣言されると、その領域がスタックやヒープ空間に確保されるという風に理解しました。 一方オブジェクト指向言語にて、あるクラスのインスタンスを生成した場合を考えます。インスタンスには変数の他に関数やメソッドといった処理手続きが含まれていますよね。その手続きをコンパイルしたコードは何処に置かれているのでしょうか? 手続きのコードもヒープ空間に確保された各インスタンスの領域にコピー(?)されて実行されるのでしょうか。それとも処理手続きの実体は実行プログラムに含まれていて全インスタンスで共通で利用するようになっているのでしょうか。 初心者の素朴な疑問です、ご回答いただければ幸いです。