web-dev-qa-db-ja.com

C ++での関数型プログラミング

誰かがC++で関数型プログラミングをどのように行うかを教えてくれますか?参考にできる良いオンライン資料はありますか?

ライブラリFC++について知っていることに注意してください。 C++標準ライブラリだけでそれを行う方法を知りたいです。

ありがとう。

52
Red Hyena

2014年8月更新:この回答は2009年に投稿されました。C++ 11はC++の関数型プログラミングの問題を大幅に改善したため、この回答は正確ではなくなりました。私はそれを歴史的記録のために下に置いておきます。

この答えは受け入れられたものとして残っていたので、私はそれをコミュニティーWikiに変えています。最新のC++を使用した関数プログラミングに関する実際のヒントを追加するために、共同で自由に改善してください。


C++ではtrue関数型プログラミングを実行できません。できることは、多大な労力と複雑さでそれを概算することです(ただし、C++ 11では少し簡単です)。したがって、このアプローチはお勧めできません。 C++は他のプログラミングパラダイムを比較的よくサポートしており、IMHOはサポートが不十分なパラダイムに屈するべきではありません。

24
Eli Bendersky

最新のC++を使用すると、驚くべき量の「関数型プログラミング」スタイルを実現できます。実際、言語は標準化以来、その方向に向かっています。

標準ライブラリには、map、reduceなどに類似したアルゴリズム(for_each、transform、neighborhood_sum ...)が含まれています。次のリビジョンC++ 0xには、プログラマーがこれらをより機能的なスタイル(ラムダ式など)で操作できるように設計された多くの機能が含まれています。

もっと楽しくするには、さまざまなBoostライブラリを調べてください。標準C++に多くの優れた機能が含まれていることを説明するために、標準C++の継続渡しスタイルの階乗関数を次に示します。

#include <iostream>

// abstract base class for a continuation functor
struct continuation {
    virtual void operator() (unsigned) const = 0;
};

// accumulating continuation functor
struct accum_cont: public continuation {
    private:
        unsigned accumulator_;
        const continuation &enclosing_;
    public:
        accum_cont(unsigned accumulator, const continuation &enclosing)
            : accumulator_(accumulator), enclosing_(enclosing) {}; 
        virtual void operator() (unsigned n) const {
            enclosing_(accumulator_ * n);
        };
};

void fact_cps (unsigned n, const continuation &c)
{
    if (n == 0)
        c(1);
    else
        fact_cps(n - 1, accum_cont(n, c));
}

int main ()
{
    // continuation which displays its' argument when called
    struct disp_cont: public continuation {
        virtual void operator() (unsigned n) const {
            std::cout << n << std::endl;
        };
    } dc;

    // continuation which multiplies its' argument by 2
    // and displays it when called
    struct mult_cont: public continuation {
        virtual void operator() (unsigned n) const {
            std::cout << n * 2 << std::endl;
        };
    } mc;

    fact_cps(4, dc); // prints 24
    fact_cps(5, mc); // prints 240

    return 0;
}

はい、少し嘘をつきました。これは階乗functorです。結局のところ、クロージャーは貧乏人のオブジェクトです...そして逆もまた同様です。 C++で使用されるほとんどの機能的手法は、ファンクター(つまり、関数オブジェクト)の使用に依存しています。これは、STLで広く見られます。

47
Derrick Turk

UPD 2018年12月

記事、講演、スクリーンキャスト、論文、図書館、ショーケースを見つけることができる資料の包括的なリストを作成しました。

C++の関数型プログラミング


私の4つの研究プロジェクトについて考えてみましょう。

このプロジェクトは、「アンバー」ゲームのプロトタイプです。このコードは、immutabilitylambdasmonadscombinators、_pure functions_、_declarative code design_などの主要な機能概念の多くを示しています。 Qt C++およびC++ 11機能を使用します。

簡単な例として、アンバーの世界が適用されたときにそれを変更する1つの大きなタスクにタスクをチェーンする方法をご覧ください。

_const AmberTask tickOneAmberHour = [](const amber::Amber& amber)
{
    auto action1Res = magic::anyway(inflateShadowStorms, magic::wrap(amber));
    auto action2Res = magic::anyway(affectShadowStorms, action1Res);
    auto action3Res = magic::onFail(shadowStabilization, action2Res);
    auto action4Res = magic::anyway(tickWorldTime, action3Res);
    return action4Res.amber;
};
_

これは、C++での一般的な機能レンズのショーケースです。 implementatoinは_Variadic Templates_を使用して構築されており、レンズを構成可能で見栄えの良いものにするためのいくつかの魅力的な(そして有効な)C++ハックです。ライブラリはトークのデモにすぎないため、最も重要なコンビネータのいくつかのみを提供します。つまり、set()view()traverse()bind()、インフィックスリテラルコンビネーターtoover()など。

(「C++レンズ」があることに注意してください project :しかし、それは本当の「レンズ」ではなく、C#またはJavaの意味でのゲッターとセッターを持つクラスプロパティに関するものです=プロパティ。)

簡単な例

_Car car1 = {"x555xx", "Ford Focus", 0, {}};
Car car2 = {"y555yy", "Toyota Corolla", 10000, {}};

std::vector<Car> cars = {car1, car2};

auto zoomer = traversed<Car>() to modelL();

std::function<std::string(std::string)> variator = [](std::string) { return std::string("BMW x6"); };
std::vector<Car> result = over(zoomer, cars, variator);

QVERIFY(result.size() == 2);
QVERIFY(result[0].model == "BMW x6");
QVERIFY(result[1].model == "BMW x6");
_

あなたはおそらくモナドについて聞いたことがあるでしょう。モナドは今や関数型プログラミングについての話の至る所にあります。それは流行語です。しかし、コモナードはどうですか? 1Dおよび2Dセルオートマトンにフードの下のコモナードの概念を示しました。その目的は、std :: futureをParモナドとして使用して、単一フローコードから並列コードに簡単に移行できることを示すことでした。このプロジェクトでは、これらの2つのアプローチのベンチマークと比較も行います。

簡単な例

_template <typename A, typename B>
UUB fmap(
    const func<B(UUA)>& f,
    const UUUUA& uuu)
{
    const func<UB(UUUA)> f2 = [=](const UUUA& uuu2)
    {
        UB newUt;
        newUt.position = uuu2.position;
        newUt.field = fp::map(f, uuu2.field);
        return newUt;
    };

    return { fp::map(f2, uuu.field), uuu.position };
}
_

このライブラリは、_Free monad_およびその他の関数型プログラミングの高度なアイデアに基づいています。そのインターフェースはHaskellのネイティブ STMライブラリ に似ています。トランザクションはモナディックに構成可能で、純粋に機能的であり、並行モデルの設計をより便利で強力にする多くの便利なモナディックコンビネーターがあります。ライブラリを使用して Dining Philosophers problem を実装しましたが、うまく機能します。これは、哲学者がフォークを取るためのトランザクションのサンプルです。

_STML<Unit> takeFork(const TFork& tFork) {
    return withTVar<Fork, Unit>(tFork, [=](const Fork& fork) {
       if (fork.state == ForkState::Free) {
           return writeTVar<Fork>(tFork, Fork {fork.name, ForkState:Taken});
       }
       else {
           return retry<Unit>();
       }
    });
}

STML<Unit> takeForks(const TForkPair& forks) {
    STML<Unit> lm = takeFork(forks.left);
    STML<Unit> rm = takeFork(forks.right);
    return sequence(lm, rm);
}
_
11
graninas

できない C++での真の実際の関数型プログラミングとは思わない。しかし、それは確かにそれを使用する最も簡単または自然な方法ではありません。また、マインドセット全体ではなく、機能に似たイディオムをいくつか使用することもできます(つまり、「流暢なスタイル」)

私のアドバイスは、関数型言語を学び、おそらくSchemeから始め、次にHaskellに移ることです。次に、C++でプログラミングするときに学んだことを使用します。多分あなたは明白な機能的なスタイルを使わないでしょう。しかし、最大の利点が得られる可能性があります(つまり、不変構造を使用します)。

5
Javier

Functional Cと呼ばれる本が、Pieter HartelとHenk Mullerによって作成されており、役立つ場合があります。それでも利用できる場合は、その情報へのリンクを参照してください。 is here 。IIRCそれほど悪くはなかった。

1
rvirding

おそらく少し遅れますが、他の人が探している人のために-C++の関数型プログラミング拡張機能としてluaを使用しています。 lua

0
daven11