ラムダ式はいらない子?

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
遊び人
記事: 38
登録日時: 1年前

ラムダ式はいらない子?

#1

投稿記事 by 遊び人 » 2週間前

お世話になっております。
C++11にて、ラムダ式なるものが追加されました。
「猫でもわかるC++プログラミング」には、述語関数で利用するとちょっぴり便利などと言うようなことがかかれていますが、正直私ほどのレベルではこれだけのためにわざわざラムダ式を追加する必要があったのかな?と感じてしまいます。わざわざ追加されているということは、何か有用な使い方があるはず!
是非ご教授ください。

コード:

#include <iostream>
#include <algorithm>
#include <vector>
#include <windows.h>
using namespace std;

int main() {
	vector<int> v;
	vector<int>::iterator s, e;

	for (int i = 0; i < 5; i++)	// vに値を追加する
		v.push_back(i * i);
	s = v.begin();
	e = v.end();

	// 述語関数をラムダ式で記述して、for_eachを呼び出す
	for_each(s, e, [](int x) {
		if (x % 2 == 0)
			cout << x << "は偶数です。" << endl;
		else
			cout << x << "は奇数です。" << endl;
	});

	system("pause");
	return 0;
}

Rittai_3D
記事: 518
登録日時: 5年前

Re: ラムダ式はいらない子?

#2

投稿記事 by Rittai_3D » 2週間前

ラムダ式は「その場でスグに関数が作れる」ところが利点だと思います。
が、「ラムダ式は本当に必要なのか?別に必要ないのでは?」と疑問を抱いている間には有用性に気がつかないものでしょう。
もっと言うと、ラムダ式を使う必要がないと思うならば使わなくても問題がないので、「あ、ここでラムダ式使うと便利だ!」ということがある時までに使わなけば有用性に気づくと思います。

実行結果 https://wandbox.org/permlink/131rlsvBgffvjyYL
► スポイラーを表示
上のコードを見ていただくと、ラムダ式が一番手っ取り早く書けているのがわかると思います。
述語関数の内容が上のコードのように、外部に関数 or 関数オブジェクトを作るレベルのものでなければ、ラムダ式を使うのが楽です。

また、ラムダ式には名前をつけられるので、同じ関数内で同じ処理をしたい場合は

コード:

void hoge()
{
	auto func = []( int v ) { /* 処理する内容 */ };
	
	std::for_each( std::begin( array1 ), std::end( array1 ), func );
	std::for_each( std::begin( array2 ), std::end( array2 ), func );
}
のように書け、外部に関数 or 関数オブジェクトを作る必要がなくなります。
初心者です

遊び人
記事: 38
登録日時: 1年前

Re: ラムダ式はいらない子?

#3

投稿記事 by 遊び人 » 2週間前

なるほど、確かに短い関数の場合は、外部に関数を作って・・・とするのが少し面倒だとは思っていました。
よくよく考えてみれば、ラムダ式のような「別に無くても作れるけどあると便利だよね」というような機能は他にもたくさんありましたね。
言ってしまえばテンプレートもそれにあたりますし。

参照魚
記事: 27
登録日時: 8ヶ月前

Re: ラムダ式はいらない子?

#4

投稿記事 by 参照魚 » 2週間前

ラムダ式は周囲の変数を式の内部に閉じ込めた関数オブジェクトを簡易に生成する事ができるのが最大の利点と思っています。

コード:

void sample( void ){

	int	x = 10;

	const auto f = [=](){ printf( "%d\n", x ); }; 	// この時点の値がコピー
	x = 20;							// その後に変更しても
	func();							// 10が表示される
}
生成した関数オブジェクトを保持しておいて、後で別の場所で遅延実行させることもできます。構造体と関数ポインタでも同様の事ができますが、より簡潔に直感的に記述する事ができます。

遊び人
記事: 38
登録日時: 1年前

Re: ラムダ式はいらない子?

#5

投稿記事 by 遊び人 » 1週間前

変数のように扱える関数という点も面白いですね

アバター
Dixq (管理人)
管理人
記事: 1656
登録日時: 8年前
住所: 北海道札幌市
連絡を取る:

Re: ラムダ式はいらない子?

#6

投稿記事 by Dixq (管理人) » 1週間前

# 北海道在住のため、地震でしばらくこれませんでした・・。

ラムダ式はStreamAPIと相性がよく、関数型プログラミングを実現させてくれる点も見逃せません。
もし命令型プログラミングと、関数型プログラミングの違いが分からなければ、リンク先をご覧ください。
https://qiita.com/munieru_jp/items/6c0dbada463e00429fd1

以下のコードはリンク先からの引用です。

コード:

List<String> numTextList = Arrays.asList("0", "1", null);

List<Integer> numList = new ArrayList<>();
for(String numText : numTextList){
  if(numText == null){
    continue;
  }
  int num = Integer.parseInt(numText);
    numList.add(num);
}
今、リストにある、nullを除外した"0"と"1"を取り出してint型のリストに詰めたい時、
従来の命令型プログラミングだとこのように書かなければなりません。
やりたいことは
「nullを除いて"0"と"1"を取り出して」
「文字列をint型に変換して」
「リストに詰める」
ことです。これをこの文章通りにプログラミングできればバグも減り、
コードの可読性も向上するはずですが、命令型プログラミングではそれが困難です。
ここで、StreamAPIとラムダ式を用いて書き直してみます。

コード:

List<String> numTextList = Arrays.asList("0", "1", null);
List<Integer> numList = numTextList.stream()
        .filter(s -> Objects.nonNull(s))
        .map(s -> Integer.parseInt(s))
        .collect(Collectors.toList());
これを改めてみてみましょう。
「nullではない物をフィルターにかけて」
「文字列をint型に変換して」
「リストに詰める」
ことをしています。
どうでしょう、命令型プログラミングと関数型プログラミングはどちらが分かりやすでしょうか。
実現したいことを文章に起こした通りに実装できていると思えます。

私はこの本を持っていますが、ラムダ式はそれだけで一冊本が書けてしまうほどの使い方があります。
深く知りたければラムダ式に特化した本を読んでみるとよいかと思います。

遊び人
記事: 38
登録日時: 1年前

Re: ラムダ式はいらない子?

#7

投稿記事 by 遊び人 » 1週間前

なるほど・・・関数型プログラミングを学ぶことで、より理解が深められそうですね!
本気でラムダ式を利用しようとなると、プログラミングの考え方から見直さないと駄目みたいです(笑)

見やすさ、バグの生まれにくさという点で関数型のほうが圧倒的に優れているように見えますし、コーディング時間も短縮できそうで、関数型が流行るのも納得という感じです。

全くの余談ですが、関数型よりも命令型のほうが「俺今めっちゃプログラミングしてる」感はありあすね。

アバター
Dixq (管理人)
管理人
記事: 1656
登録日時: 8年前
住所: 北海道札幌市
連絡を取る:

Re: ラムダ式はいらない子?

#8

投稿記事 by Dixq (管理人) » 1週間前

"「俺今めっちゃプログラミングしてる」感"を大事にしたい頃は命令型プログラミングをしていてよいと思います。
そのうち、より効率的に可読性高く、かつマルチスレッドセーフにということを考えていくと
自然と関数型プログラミングの考え方に出会うことがあると思うので、
必要だという段階に達した時にまた考えればよいと思います。
JavaだってJava7まではStreamAPIはありませんでしたし、C++もC++11,14まではありませんでした。
それまでは無しでもみんな大規模なシステムを作っていたのです。
しかし、より効率よく、バグの少ないコーディングをするかと突き詰めていくと今とは違うコーディングに行きつきます。(その時それが関数型プログラミングではないかもしれません)
その段階を経るのは悪いことではありません。
色んな段階で色んなコーディングを経験してみてください。

アバター
tk-xleader
記事: 150
登録日時: 7年前
住所: 関西某所
連絡を取る:

Re: ラムダ式はいらない子?

#9

投稿記事 by tk-xleader » 1週間前

C++のラムダ式の話であることを前提に回答しますが、C++のラムダ式は関数オブジェクトの糖衣構文なので、ラムダ式がなくても同等のコードを記述することができます。

ただ、1回しか書かないような述語関数のために一々関数オブジェクトを書くのは面倒ですし、ラムダ式のうち、変数キャプチャを使うような類のものを関数オブジェクトとして記述するというのは分かりづらいコードになりますし面倒ですね。

ラムダ式の方が便利な例をということであれば、最初に挙げられた例のfor_eachの出力先をstd::coutではなく、std::ofstreamで開いたoutput.txtとするようなコードをラムダ式ありと無しで書いてみるといいかと思います。

遊び人
記事: 38
登録日時: 1年前

Re: ラムダ式はいらない子?

#10

投稿記事 by 遊び人 » 1週間前

一見何が変わるのかわかりませんでしたが、いざ作ってみるとあ~ら不思議。なかなかうまくいかない...
ラムダ式では、キャプチャの機能を使用することで簡単に外部の変数などにアクセスできますが、関数オブジェクト等の場合、for_eachなどのテンプレートでは直接関数の引数を記述することができないため、非常に面倒ったんですね。
ラムダ式の他にも様々な方法がありましたが、どれも非常に分かりづらい&面倒くさいでラムダ式を使わない理由がないと思うほどです。
当たり前のように利用していたキャプチャですが、なくなって初めてその大切さに気付きました。

コード:

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <functional>
#include <Windows.h>
using namespace std;

// グローバル変数
ofstream of2;				// output2.txtに書き込む用

// ファイルを開く
void OpenFile(ofstream &of, const char* file_name) {
	try {
		of.open(file_name);
	}
	catch (const ofstream::failure e) {
		cout << "msg:" << e.what() << endl;
	}
}

// 関数オブジェクト
struct Output {
	void operator() (int x) {
		of2 << x << ((x % 2) ? "は奇数です" : "は偶数です") << endl;
	}
	
};

int main() {
	vector<int> v;
	vector<int>::iterator s, e;

	for (int i = 0; i < 5; i++)	// vに値を追加する
		v.push_back(i * i);
	s = v.begin();
	e = v.end();

// 述語関数をラムダ式で記述して、for_eachを呼び出す
	ofstream of;
	OpenFile(of, "output.txt");	
	for_each(s, e, [&of](int x) {
		of << x << ((x % 2) ? "は奇数です" : "は偶数です") << endl;
	});
	of.close();

// 述語関数をラムダ式なしで記述
	OpenFile(of2, "output2.txt");
	for_each(s, e, Output());
	of2.close();

	system("pause");
	return 0;
}

かずま

Re: ラムダ式はいらない子?

#11

投稿記事 by かずま » 1週間前

遊び人 さんが書きました:
1週間前
ラムダ式では、キャプチャの機能を使用することで簡単に外部の変数などにアクセスできますが、関数オブジェクト等の場合、for_eachなどのテンプレートでは直接関数の引数を記述することができないため、非常に面倒ったんですね。
それがグローバル変数を使った理由ですか?
関数オブジェクトを次のよう書けばよいのではありませんか?

コード:

#include <iostream>   // cout, endl
#include <fstream>    // ofstream
#include <vector>     // vector
#include <algorithm>  // for_each
using namespace std;

int main() {
	vector<int> v(5);
	for (int i = 0; i < v.size(); i++) v[i] = i * i; // vに値を入れる
	auto s = v.begin(), e = v.end();

// 述語関数をラムダ式で記述して、for_eachに渡す
	{
		ofstream of("output.txt");
		if (!of) return cout << "can't create output.txt\n", 1;
		for_each(s, e, [&](int x) {
			of << x << ((x % 2) ? "は奇数です" : "は偶数です") << endl;
		});
	}
// 述語関数を関数オブジェクトで記述して、for_eachに渡す
	struct Output {
		ostream& of;
		Output(ostream& o) : of(o) {}
		void operator() (int x) {
			of << x << ((x % 2) ? "は奇数です" : "は偶数です") << endl;
		}
	};
	ofstream of2("output2.txt");
	if (!of2) return cout << "can't create output2.txt\n", 2;
	for_each(s, e, Output(of2));
}
元のプログラムで、例外をキャッチしようとしていますが、
ファイル名を "x/output.txt" などにしてオープンできない
ようにしても例外は発生しません。
また、例外をキャッチしたらプログラムは停止しませんから、
オープンできてないファイルへの書き込みを行おうとしますよ。
エラーにはなるけど。

遊び人
記事: 38
登録日時: 1年前

Re: ラムダ式はいらない子?

#12

投稿記事 by 遊び人 » 1週間前

ファイルをオープンできなかった場合の処理について普通に失念しておりました・・・ハズカシイ

その記述は一番最初に思いつきましたが、下記のようにしていたため、エラーを出しまくって諦めてました・・
コントラスタで引数を取ることで、そのような書き方ができたんですね。

コード:

// 述語関数を関数オブジェクトで記述して、for_eachに渡す
	struct Output {
		void operator() (ofstream &o, int x) {
			of << x << ((x % 2) ? "は奇数です" : "は偶数です") << endl;
		}
	};

返信

“C言語何でも質問掲示板” へ戻る