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

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

新規登録して質問してみよう
ただいま回答率
85.37%
デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

1回答

11191閲覧

カリー化ってどんな時に便利なのですか?

Chironian

総合スコア23272

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

プログラミング言語

プログラミング言語はパソコン上で実行することができるソースコードを記述する為に扱う言語の総称です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

2クリップ

投稿2015/11/10 05:58

カリー化でググると多数の解説記事がでてきます。
ということは、それなりに便利なテクニックなのかな?と思うのですが、どのサイトを見てもカリー化のメリットがよく分かりません。
どうも部分適用(一部の引数を固定した関数を変数に設定する)する際に、一度カリー化することが多いようですが、カリー化せずに直接部分適用した方が簡単で良いと思うのですよ。

カリー化の実用面でのメリットってあるのでしょうか?
(あ、部分適用のメリットは理解してます。汎用な関数を様々なケース向けに限定して使いたい時など重宝します。)


私はC++erなのでC++14によるカリー化を見つけました。
C++14の強化されたラムダ式と戻り値の型推論を使えば、カリー化する関数テンプレートを簡単に作れるようです。
これを使って、add()関数をカリー化して部分適用する簡単なサンプルを作ってみました。

C++

1#include <iostream> 2// curry()はhttp://notmoon.hateblo.jp/entry/2015/08/05/115525からの引用 3template<typename F> 4auto curry(F f) 5{ 6 return [f](auto a){return [f, a](auto... args){return f(a, args...);};}; 7} 8int add(int x, int y) 9{ 10 return x+y; 11} 12int main() 13{ 14 auto add1c = curry(add)(10); 15 std::cout << add1c(20) << "\n"; 16}

でも、下記のようにカリー化せずに部分適用することもできます。
こちらの方が直接的で分かりやすくないでしょうか?

C++

1#include <iostream> 2template<typename F, typename T> 3auto partial(F f, T a) 4{ 5 return [f, a](auto... args){return f(a, args...);}; 6} 7int add(int x, int y) 8{ 9 return x+y; 10} 11int main() 12{ 13 auto add1p = partial(add, 10); 14 std::cout << add1p(20) << "\n"; 15}

両方共、msvc2015とMinGW 4.9.2(-std=c++14オプション)で動作確認しています。

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

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

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

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

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

guest

回答1

0

ベストアンサー

カリー化はプログラミング上のテクニックというよりは、理論的な面での扱いやすさ、という側面が大きいです。

あらゆる関数を1引数の関数として統一的に扱えるので、理論的に考える上では扱いが楽になります。

また、Haskellでは関数の引数が1つと決まっていて、多引数に見える関数は「複数の値をまとめたタプル1つを取る」、あるいは「カリー化されている」のどちらかです。

投稿2015/11/10 06:25

maisumakun

総合スコア145930

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

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

Chironian

2015/11/10 07:31

回答ありがとうございます。 やはり実用面のテクニックではなさそうですね。 カリー化しても本質的には何も変わらないので理論面で扱いやすくなるのが不思議な印象ですが、数学的な理論はびっくりするような発想の塊ですから、有用な使い方があるのでしょうね。
eripong

2015/11/10 07:47

そこまで数学に詳しくないですが、 1引数の場合のみについての証明や定理は、 1引数、2引数、、、、n引数の場合すべてについてより、 シンプルになるからだと思います。
Chironian

2015/11/10 08:54

単に引数の形式をとってないだけで、該当の変数には相変わらずアクセスしてますから、それを忘れることができるのが不思議なのですよ。 でも、eripongさんがおっしゃる通り、「形式的」な引数が問題となるケースがあるのだろうと思います。私には想像も付きませんが、数学の世界はものすごい発想の転換がありますから、必要な考え方なのだと思います。 逆に言えば、そのような発想と無関係なケースではカリー化を取り扱う必要はなさそうなのでちょっとホッとしています。
eripong

2015/11/10 10:58

カリー化が数学や、言語処理系の実装などを行うのでなければ、 取り扱う必要がなさそうなのは、同意します。 ただ、統一的に記述できることは、数学に限らず、 プログラムの実装においても有用と思います。 ポリモーフィックな動きを自分で実装することを考えてください。 Parentクラスとそれを継承したChild1、Child2、、、Childnクラスがあった時に、 Parentクラスの変数pに対して、 p.call(); と呼ぶだけなのと、 if (pがChild1のインスタンスなら) { Child1 c1 = (Child1)p; c1.call(); } else if (pがChild2のインスタンスなら) { Child2 c2 = (Child2)p; c2.call(); } ... } else if (pがChildnのインスタンスなら) { Childn cn = (Childn)p; cn.call(); } と書かなければいけないのは、実際の意味は同じですが、 実装量が非常に違います。
Chironian

2015/11/10 12:55

> ただ、統一的に記述できることは、数学に限らず、 > プログラムの実装においても有用と思います。 確かに!! プログラム開発では、本質的に同じ論理構造(例えば実数の加算と複素数の加算)を同じ形式で記述できるとメリットでかいですね。 そして、本質的に異なる構造(例えば2個の加算と可変個の加算)を無理に同じ形式で記述すると可読性が低下するので、デメリットが大きいと思います。 要は使いドコロですね。 部分適用以外のケースではパッとは思いつきませんが、本質的には引数が1つで良いのに、何らかの要因で引数が複数あるような関数をカリー化するとメリットある筈ですね。 アドバイスありがとうございます。
raido

2019/04/04 09:42 編集

僕もあまり詳しくないのですが、細かいことを言うとちょっと違う気がします。上のC++コードにある通りになりますが、二引数の関数f(x,y)に対するカリー化とは(lambda (x) (lambda (y) (f x y)))のようなオブジェクトを返すもので、部分適用とは(lambda (y) (f x y))のような形を返すものです。一見違いがないような気がしますが、下記のような状況では、違いが表れてきます。 (define (curry f) (lambda (x) (lambda (y) (f x y)))) (define (partial f x) (lambda (y) (f x y))) (define numbers '(1 2 3)) ;; すぐに値が得られる (map (partial + 1) numbers) ;; 値を計算する手続きの列が得られる (define delayed (map (curry +) numbers)) ;; ここで初めて計算を行う (map (lambda (x) (x 1)) delayed) あまりいい例ではありませんね、ありがとうございました。  私も筋がねいりのscheme使いではないので、どうでもいいような気がしますが、評価を遅らせることで、いろいろしのぐというのはマクロなどを作るときに必須のテクニックらしいので、多分重要なのでしょう。  上の例でも、足し算することを決めた後で、やっぱりリストの二番目だけ2足さないとダメ、とかそういう状況になっても、戻ってきているのが手続きであれば対応できるとか。いずれにしろ、map関数は何を渡されたのか知る必要はありません(たぶん、ここが最大のメリット)。  あるいは単なる足し算ではなく、ストリームのようなものに対して、どんな計算をするか?だけ定義しておいて、値が手に入った段階で、全部適用とかしたい場合にも使えそうです。たとえば、演算の構造をクライアント側で決めてもらって、ライブラリ側で実際の演算を実行していき、ごにょごにょするとか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問