質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Q&A

解決済

5回答

2396閲覧

ブロック、Proc、Lambdaの使いどころ

yoshiky

総合スコア105

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Ruby on Rails

Ruby on Railsは、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

0グッド

0クリップ

投稿2017/05/02 00:53

お世話になっております。
Rubyで使われるブロックやProc,lambdaの使いどころがよくわかりません。
検索すれば情報がたくさん出てくるので、コードの理解はできるのですが、
「なぜそのように書かねばならないのか(または書いたほうがいいのか)」
が理解できません。

具体的には、わざわざ難しく書かなくても普通の関数ではいけないのかと思ってしまいます。
下記に例を示します。

ruby

1# http://www.atmarkit.co.jp/ait/articles/1409/29/news035_2.html 2# サンプルコード block02.rbを一部簡略化 3 4def arithmetic_sequence(init: 1, diff: 1, count: 10) 5 current = init 6 count.times do 7 yield(current) 8 current += diff 9 end 10end 11 12arithmetic_sequence(init: 2, diff: 3, count: 5) do |n| 13 puts n 14end

このコードは下記のように書き換えられると思います。

ruby

1def arithmetic_sequence(init: 1, diff: 1, count: 10) 2 current = init 3 count.times do 4 puts current # <= ここがyieldから変わっただけ 5 current += diff 6 end 7end 8 9arithmetic_sequence(init: 1, diff: 4, count: 10)

例が悪いかもしれませんが、要はyieldと書いてあるところでブロック内の処理をする、ということだと思います。

ならばブロック内の処理をdef...end で関数定義し、yieldと書いてあるところで呼び出せばいいだけ、と思ってしまいます(または上記例のようにyieldの箇所をそのままputs のように置き換える)

ブロックやProcを説明しているサイトは大抵「例が悪いが」とか「ありがたみが薄いかも」という注釈があるため、処理自体は理解できても、メリットが伝わりません。どういう場面で使うのか、具体的にメリットが感じられる例をご紹介頂けないでしょうか。
どういう流れでProcやLambdaを定義しようと思いつくのか、その思考回路でも構いません。
Qiitaの記事に『例えば、開発者Aが「5という数で好きな事をしてもらうメソッドを作ろう」と考えた場合』とあるような形です(この例もメリットは感じられませんでしたが。。)

依頼ばかりの質問で申し訳ないですが、よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

yoorwmさまの言われていることで全てを説明しているのですが
さすがにこれではyoshiky様が納得しないと思うので。補足させていただきます。

block yieldの強み(の一つ?)は、使うタイミングで簡単に、関数を書き換えて使うことができる
ということです。

Rubyを語る上で欠かせないmap関数を例にとってみてみましょう。

mapは配列から配列を返す関数です。

これをblockを渡さないで実行すると自分自身を返す何もしない関数になります。(正確には違いますが)

ruby

1[1,2,3].map #=>[1,2,3] 2

こんなもの何に使うんだよ!!
ただの恒等写像じゃん!!
と思うかもしれませんがblockを渡すとあら不思議
各項を2倍したり

ruby

1[1,2,3].map{|a|a*2} #=>[2,4,6]

各項を文字列にしたり

ruby

1[1,2,3].map{|a| a.to_s } #=>['1','2','3']

奇数だけを抽出したり

ruby

1[1,2,3].map{|a| a if (a % 2) == 1 } #=>[1,nil,3]

素数だけを抽出したり

ruby

1[1,2,3].map{|a| a if a.primary? } #=>[nil,2,3]

3の倍数の時にあほになったり

ruby

1[1,2,3].map{|a| (a % 3) == 0 ? "( ^ิ౪^ิ )" : "(・`ω´・ ○)" } #=>["(・`ω´・ ○)","(・`ω´・ ○)","( ^ิ౪^ิ )"]

あなたはこれらのことをするのに
関数をいちいち定義して、読み込んで使うのですか?

ツールを自由度を高く作り、使用者が使用時に好きにカスタマイズして使う
これはRubyの哲学の一つです。(DRYの一部って言われたら反論のしようはございませんが...)

ProcやLambdaは奥が深く、良さの1割も説明していないと思いますが
(そもそも、私も半分理解してるのかさえ怪しい)
yoshiky様の疑問がトンチンカンなことは理解できてくれると思います。

投稿2017/05/02 02:42

moke

総合スコア2241

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshiky

2017/05/02 05:15 編集

ご回答ありがとうございます。 頂いた例は、分かりやすかったです。おっしゃる通り、関数を定義してご提示いただいた例を実現するには、その分関数を作る必要があるので、冗長になりますね。 ブロックやProcの使用例がアプリ開発で使うような内容でないため、イメージがつきません。。 普段はネット上の例しか見れないので、実際のアプリ開発においてどのように使われるのかが想像できませんが、それはネットを巡回して公開されているソースでも読むしかないですね。。 思いつく限りだと 「csvファイルから売上ファイルをインポートするが、取引先に応じてファイルのフォーマットが異なるので、取引先ごとに取込用に変換する処理を定義し、取込時にはその「処理」を引数にしてインポートする」とかでした。
moke

2017/05/02 05:59

ああ、そっちがメインだったんですね。すみません、早とちりでした。 基本csvファイルのようなライブラリとか根本的なところで使いますものね Railsではあらかじめyieldを使ったmethodが組み込まれているので 作るタイミングは少ない…。 そうだ、helper methodとかを作ると、ありがたみがわかるかもしれません。
guest

0

ベストアンサー

決まった事しかしないなら、その決まった処理のコードを書けばいいです。

いろんな事を出来るようにするために、その処理を引数化します。

あなたの言っている事は、「

Ruby

1def arithmetic_sequence 2 current = 1 3 10.times do 4 puts current 5 current += 1 6 end 7end

と書けば、呼び出し側から初期値、増分、終了値を指定する必要が無くなります。」
と言ってるのと本質的には同じです。

また、

ならばブロック内の処理をdef...end で関数定義し、yieldと書いてあるところで呼び出せばいいだけ、と思ってしまいます

というのは、「引数にしなくても、グローバル変数で渡せばいいじゃない」と言っているのと同じです。

標準のメソッドで、each map sort_by などなど便利な物がたくさんありますが、
mapで呼び出してもらうメソッドを、(引数で渡すのじゃなくて)map_callbackという名前で事前に定義した上でmapを呼んでください」という仕様だと、mapを複数回呼ぼうとした時点で破綻します。

#追記
上記の後半(「また、」以降)が理解出来ないと言う事なので。

yoshikyさんの言ってるのはこういう事ですよね?

  1. 処理を引数で渡さなくても、呼び出されるメソッドの中に直接処理を書けばいいじゃないか
  2. それだと、他のことをしたいときに、メソッドを毎回書き直さないといけないじゃないか
  3. じゃあ、書き直さなくて言いように、処理にdef~endで名前を付けて、その名前をメソッドの中から呼べばいいじゃないか

「その考えはこれと同じだよ」と私が書いたのは、

  1. 引数なんかで渡さなくても、メソッド中でその値を直接書けばいいじゃないか
  2. それだと、呼び出し時に値を変えたい時に困るじゃないか。メソッドにした意味がない
  3. じゃあ、グローバル変数経由で値を渡せば引数が要らないですよ

こちらは map(&:hoge)などの記法のことでしょうか。こちらもこのコードの意味は理解できても、メリットが思いつかない、

そもそも、メソッドを作るメリットを何だと思っていますか?そこから分からないですか?
メソッドに引数という仕組みがあるメリットは理解出来ますか?そこから分からないですか?

投稿2017/05/02 01:25

編集2017/05/02 04:50
otn

総合スコア84423

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshiky

2017/05/02 01:46

ご回答ありがとうございます。 「いろんなことができるようにする」ためにブロックなりProcの機能がある、ということは理解しているつもりです。しかし、具体的な例が思いつかないのです。確かにおっしゃる通り「決まった事しかしないなら、その決まった処理のコードを書けばいい」のですが、、、どういう思いつきで「ここはProcを使おう」と思い至るのでしょうね。 また「呼び出し側が(ブロック内に)処理を定義する」メリットがいまいち分かりません。「定義した処理を呼び出す」と大差がないように思えてしまいます。
otn

2017/05/02 02:08

> 「定義した処理を呼び出す」と大差がないように思えてしまいます。 回答の後半にその答えを書いたつもりですが、いかがでしょうか?
yoshiky

2017/05/02 02:43

申し訳ないです。お恥ずかしいですが、ご回答の後半は理解できませんでした。 > 「引数にしなくても、グローバル変数で渡せばいいじゃない」と言っているのと同じ 本質の話をして頂いたのかもしれませんが、引数とかグローバル変数がどう関係するのか分かりませんでした。 > mapで呼び出してもらうメソッドを… こちらは map(&:hoge)などの記法のことでしょうか。こちらもこのコードの意味は理解できても、メリットが思いつかない、もっと言えばどういう経緯でこういうコードを書こうと思ったかが分からないのです。(そのため、ブロックとかProcを一から勉強しようと思い、こちらに投稿させて頂いた次第です) 阿呆を相手にしていると疲れるかと思います、、申し訳ないです。。
otn

2017/05/02 04:52 編集

追記しました。最後の2つの問いに答えてください。
yoshiky

2017/05/02 05:24

お付き合い頂き、ありがとうございます。 メソッドを作るメリットは「処理の共通化、部品化」と捉えております。 メソッドに引数という仕組みがあるメリットは「共通化・部品化した処理を更に汎用的にするため」と解釈しています。 > じゃあ、グローバル変数経由で値を渡せば引数が要らないですよ 順序立ててご説明頂き、ありがとうございます。 例えば2つの値を足し算する関数sumがあったとして、引数を使わないで実現するならグローバル変数を2つ定義し、sum内でそのグローバル変数同士を加算すればよく、加算する値を変えたいときは定義したグローバル変数の値を変えてあげればよい、というように理解しました。 確かにグローバル変数を使う例でも要件は満たせるが、加算したい値が増えたら?減算したい場合は? ということを考えると、値ではなく「処理」を渡したほうが効率的、というように思い至ることができました。
otn

2017/05/02 06:54 編集

ご理解の通りで良いと思います。 「処理」を引数で渡せないと、map や sort_by のような汎用メソッドが書けないです。 「処理をメソッドとして別途定義して、そのメソッドの名前を引数で渡す」というやり方でも同じことが出来ます。 名前を付けるほどではないその場限りの処理であれば、名前を付けずにでも書ける方が楽です。
guest

0

リストの各要素を

  • 2倍する。
  • 3倍する。
  • 4倍する。
  • ...

に対してそれぞれメソッドを作るのは、
「リストの各要素を何倍かする」という部分は共通ですから無駄ですね。
それだったら何倍するかは引数で持たせて、

  • n倍する。

でいいですね。

同様に、
リストの各要素を

  • n足す。
  • nで割る。
  • n倍する。
  • 平方根を取る。
  • 0以下だったら0にする。

に対してそれぞれメソッドを作るのは、
「リストの各要素に何かする」という部分は共通ですから無駄ですね。
それだったら何をするかは引数で持たせて、

  • リストの各要素に関数fを作用させる。

でいいわけです。

こういう発想でできているのがmapです。


質問文のこれ

Ruby

1def arithmetic_sequence(init: 1, diff: 1, count: 10) 2 current = init 3 count.times do 4 puts current 5 current += diff 6 end 7end 8 9arithmetic_sequence(init: 1, diff: 4, count: 10)

putもしつつ、ファイル書き出しもしたくなったらどうしますか?
arithmetic_sequence_putarithmetic_sequence_write_fileを作りますか?
もしその2つを作った後に、共通部分にバグや変更があったらどうしますか?

投稿2017/05/02 02:23

編集2017/05/02 02:46
ozwk

総合スコア13512

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshiky

2017/05/02 04:58

ご回答ありがとうございます。 「putもしつつ、ファイル書き出しもしたくなったらどうしますか?」という部分は合点がいきました。数字や文字列、配列などを引数に渡す時と同様、ブロックの形で「処理(関数)」を引数に渡し、渡された側でその「処理(関数)」を実行することで共通化を図っている、というように理解しました。 私の例で言えば、putsだけでなくファイル書き出しが追加、または別の処理ではputsではなくDBに保存する、また別の処理ではDBに保存しつつファイルにも書き出す、、、などなど、「処理」の部分を引数に渡してあげることで、if で分岐させたりせずに冗長なコードをすっきりさせることができそうですね。 残念ながら他のいい例が思いつかなので、その辺りは公開されているソースコードでも読むしかないですね。。
guest

0

すみません。
回答ではないのですが、コメントへの返信も込みで
全く私と同じ考えだったのでコメントさせて頂きました。
(全く同じ内容でたった今質問をしようとしていました笑)

例えば、
ログイン確認処理が必要なアプリで、
before_acitonが便利な理由として、

全メソッドでユーザがログイン済みかどうか確認するのは面倒だから
ログイン済みか確認する処理を独立したメソッドとして登録しておく。

そして、
それをbefore_actionに登録しておくことで、
全メソッドでいちいちログイン確認しなくても
一箇所で宣言だけしてあとはそれを呼ぶだけでいいので
「可読性が上がり、変更も容易になる」というメリットがある。

というのは腹落ちする。

しかし、procに関しては
本当に実際の開発時に便利な例というのが探しても見当たらないので
メリットがよくわからないということですよね笑

(具体的な例といえば、yoshikyさまが仰る通り『例えば、開発者Aが「5という数で好きな事をしてもらうメソッドを作ろう」と考えた場合』というような例なのですが、これも実際の現場で活かせるメリットが感じられず、結局何が嬉しいのかわからずじまいです。。)

私も
procが重要か重要でないかという話ではなくて
現場の開発においてこういう時は(使わないより)使った方が便利という具体例を知りたいです。

ご質問から1年経たれて
もしyoshiky様が
こういう時にprocを使ったら便利だったよというような例があれば
ぜひご教示頂きたいです。(逆に質問することになってしまい申し訳ありません。)

投稿2018/11/30 03:44

takoyaki9

総合スコア9

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

yieldを使っている部分は、俗にジェネレータと呼ばれます

ジェネレータで作ると、呼び出し側で好きなタイミングで処理させる事が出来ますので、部品として使えるようになります。

投稿2017/05/02 00:59

yoorwm

総合スコア1305

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yoshiky

2017/05/02 02:50

ご回答ありがとうございます。 >呼び出し側で好きなタイミングで処理させる事が出来ます こちらは関数でもできるのではないでしょうか?例えば先述の私の例だと、 「引数の値をputsで出力する」と言う関数を def hoge(arg) puts arg end などと定義して、呼び出したい箇所で hoge (123) とすればいいのでは、、と思ってしまいます。 ブロックで処理するメリットが見出せませんでした(例が悪いのでしょうが)。 「ブロックを使ったほうがいい!」と言う例があればご教示いただきたいです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問