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

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

ただいまの
回答率

88.09%

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

解決済

回答 1

投稿

  • 評価
  • クリップ 2
  • VIEW 6,979

C++総合1位

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

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


私はC++erなのでC++14によるカリー化を見つけました。
C++14の強化されたラムダ式と戻り値の型推論を使えば、カリー化する関数テンプレートを簡単に作れるようです。
これを使って、add()関数をカリー化して部分適用する簡単なサンプルを作ってみました。
#include <iostream>
// curry()はhttp://notmoon.hateblo.jp/entry/2015/08/05/115525からの引用
template<typename F>
auto curry(F f)
{
    return [f](auto a){return [f, a](auto... args){return f(a, args...);};};
}
int add(int x, int y)
{
    return x+y;
}
int main()
{
    auto add1c = curry(add)(10);
    std::cout << add1c(20) << "\n";
}
でも、下記のようにカリー化せずに部分適用することもできます。
こちらの方が直接的で分かりやすくないでしょうか?
#include <iostream>
template<typename F, typename T>
auto partial(F f, T a)
{
    return [f, a](auto... args){return f(a, args...);};
}
int add(int x, int y)
{
    return x+y;
}
int main()
{
    auto add1p = partial(add, 10);
    std::cout << add1p(20) << "\n";
}
両方共、msvc2015とMinGW 4.9.2(-std=c++14オプション)で動作確認しています。
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+4

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/11/10 19: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();
    }
    と書かなければいけないのは、実際の意味は同じですが、
    実装量が非常に違います。

    キャンセル

  • 2015/11/10 21:55

    > ただ、統一的に記述できることは、数学に限らず、
    > プログラムの実装においても有用と思います。
    確かに!!
    プログラム開発では、本質的に同じ論理構造(例えば実数の加算と複素数の加算)を同じ形式で記述できるとメリットでかいですね。
    そして、本質的に異なる構造(例えば2個の加算と可変個の加算)を無理に同じ形式で記述すると可読性が低下するので、デメリットが大きいと思います。
    要は使いドコロですね。

    部分適用以外のケースではパッとは思いつきませんが、本質的には引数が1つで良いのに、何らかの要因で引数が複数あるような関数をカリー化するとメリットある筈ですね。

    アドバイスありがとうございます。

    キャンセル

  • 2019/04/04 18:23 編集

    僕もあまり詳しくないのですが、細かいことを言うとちょっと違う気がします。上の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関数は何を渡されたのか知る必要はありません(たぶん、ここが最大のメリット)。
     あるいは単なる足し算ではなく、ストリームのようなものに対して、どんな計算をするか?だけ定義しておいて、値が手に入った段階で、全部適用とかしたい場合にも使えそうです。たとえば、演算の構造をクライアント側で決めてもらって、ライブラリ側で実際の演算を実行していき、ごにょごにょするとか。

    キャンセル

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

  • ただいまの回答率 88.09%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る