2016-10-19 17 views
6

コンテナと関数の両方に共通の抽象化があります。私はHaskellでそれを学んだので、C++で実装しようとしています。テンプレートを使用した関数型オーバーロード

ほとんどのC++プログラマはSTDに精通::変換、おおよそBへのタイプAから関数与え話している、あなたはあなたがで関数を変換することができ

タイプBのコンテナにタイプAのコンテナに変換することができます同様の方法で、AからBへの関数fooが与えられたとき、ZをAとする関数バーを関数fooに変換することができます。バーをZからBにします。実装は簡単です。単なる合成です。

汎用プログラミングの抽象概念を反映するために、関数fmapをコンテナと関数に定義したかったのです。

コンテナはしかし、機能のために類似した機能が、私は非常に神経質になり(私はこれは完全には一般的ではありません知っている)

template <typename A, typename Func> 
auto fmap(Func f, vector<A> in) { 
    vector<decltype(f(in[0]))> out_terms{}; 
    for(auto vec : in) 
    out_terms.push_back(f(vec)); 
    return out_terms; 
} 

簡単でした。

template <typename FuncT, typename Func> 
auto fmap(FuncT f, Func in) { 
    return [f, in](auto x){ 
    return f(in(x)); 
    }; 
} 

テンプレートは呼び出し可能なもの以外のものを専門としませんが、これは過負荷の解決を混乱させることになります。名前空間をきれいに保つために、関数型への解決を制限するために、テンプレートパラメータに型制約を導入したいと思います。そして私はそれをどうやって行うのか尋ねるつもりでした。

この抽象化は非常に一般的です。値へのポインタに対応するfmapがありますが、これも競合する可能性があります。

私の質問は、同じテンプレートレベルの署名を持つ2つの異なるテンプレート実装を持つことができますか?私はほとんど答えがないと確信していますが、おそらく同様のものが偽造される可能性があります。そうでない場合、過負荷を区別するために現在どのようなツールが利用できますか?特に関数型の場合。

これは私にはわかりませんが、これは概念の教科書のようです。

編集:ブースト、特にSFINAEを使用しても問題ありません。私は、ほとんどのプログラマーにとって使い慣れた、便利で標準的なソリューションを見つけることを試みています。私はfmapの名前を変更することができましたが、プログラマーはfmapを受け入れるテンプレート関数に作成を渡すことを知る必要がありました。 fmapは意味的にユニークなので、残念です。

編集2:これをどのように使用するかの簡単な例。

template <typename T> 
auto double_everything(T in){ 
    auto doublef = [](auto x){return 2*x;}; 
    return fmap(doublef, in); 
} 

それは、物事「のようなコンテナ」以上のマップへのコンテナの上にマップを一般化します。したがって、double_everything(vector<int> {1, 2, 3})は、要素が2倍になったベクトルを返します。しかし、double_everything([](int x){ return x + 1; })は、出力がインクリメント関数の出力の2倍の関数を返します。一種のリストを倍増させるようなものです。抽象化には素敵なプロパティがいくつかあります。ただそれを作り上げるだけではありません。とにかく、関数fmapの名前を変更しても問題には答えません。

編集3:テンプレートCため fmapC<A>からC<B>を満たすfmap(compose(f, g) , c) = fmap(f, fmap(g, c))に機能するAからBに機能を取ります。これは素敵な構造物のプロパティを保持しています。

範囲に対してこれを行う関数は、すでに別の名前で存在しています。しかし、rangeはタイプ上の唯一のテンプレートではありません。ここでstd::optionalためfmapです:

template<typename T, typename Func> 
auto fmap(Func f, optional<T> o) -> optional<f(*o)>{ 
    if(o) 
    return f(*o); 
    else 
    {}; 
} 

この実装は、以前の提示機能のためのfmapのように、全く任意の範囲の概念を必要としません。しかし、それはfmapの意味的要件を満たす。

私は同じ方法で異なるオーバーロードのためにfmapを定義しようとしていますが、私はカスタムマトリクスタイプのために新しいoperator *を定義します。だから、私はboost::transform_iteratorの点でfmapを喜んで定義します。次に、これらのアルゴリズムは、fmapという用語で汎用的に機能します。ここで

は、このような機能の一例です:私たちは、印刷

auto lists = vector<vector<int> > { {1, 2, 3}, {4, 5, 6} }; 
auto lists_squared = map_one_deep([](int x){return x*x;} , lists); 

lists_squaredを書いた場合、我々は代わりにoptionalsのベクトルを持っていた場合

template < 
    template<typename, typename> class Cont, 
    typename Fmappable, 
    typename Alloc, 
    typename Func> 
auto map_one_deep(Func f, Cont<Fmappable, Alloc> c){ 
    auto g = [f](Fmappable x){ return fmap(f, x); }; 
    return fmap(g, c); 
} 

は今

1 4 9 
16 25 36 

を与え、選択肢は要素を含んでいれば二乗される。

私はC++の高階関数でどのように動作するべきかを理解しようとしています。

+0

これは一般的なケースでは不可能です。私は関数 'compose'の名前を変更します。 – Barry

+0

私はこの記事を評価するのに十分なC++をもう知りませんが、あなたがやろうとしていることには少なくとも関係していると思います:[C++のFunctorパターン](https://www.fpcomplete.com/blog/2012/ 07/the-functor-pattern-in-c) – chepner

+0

SFINAEで関数のテンプレートタイプを制限することができます。 – Jarod42

答えて

0

ここで私は

template <typename FuncT, typename O, typename T> 
auto fmap(FuncT f, function<O(T)> in){ 
    return [f, in](T x){ 
    return f(in(x)); 
    }; 
} 

を発見した最も簡単な妥協点は残念ながら、これはfunction<Output(Input)>は、呼び出しサイトを飾ることを要求し、それが間接を同腹です。 私は、fmapが必要な場合にこれが最良の方法であると確信しています。

編集:You can do better.リンクは、呼び出し可能に制限する方法を提供します。これもインラインです。

機能は、次のように記述できます。

template <typename FuncT, typename T> 
auto fmap(FuncT f, tagged_lambda<T> in){ 
    return tag_lambda([f, in](T x){ 
    return f(in(x)); 
    }); 
} 

あなたは

fmap(g, tag_lambda({}(int x){return x + 1;}));

または

fmap(g, function<int(int)>({}(int x){return x + 1;}));

を呼び出すことにより、呼び出しサイトにしたいバージョンを選択することができます

テンプレートが機能し、私は関数にタグを付けることが必要であると確信しています。

ここにはブログの記事もあり、この問題について話したり、他のオプションについても説明しています。 http://yapb-soc.blogspot.com/2012/10/fmap-in-c.html

1

SFINAEで偽装することはできますが、そうしないでください。それはスタイルとイディオムの問題です。

ハスケルはタイプクラスに関するもので、プログラマーは各タイプをそれが属しているすべてのクラブにスパングルする必要があると考えています。対照的に、C++では、型の機能を指定するために、より暗黙的になりたいと考えています。 「vector」と「任意の呼び出し可能」が表示されていますが、なぜちょうどvector?なぜ任意のコンテナタイプではないのですか?私がちょうど書いたこの任意のコンテナのタイプは理由があるのでoperator()です。それでどちらを選ぶべきですか?

最終的に、技術的なあいまいさを解決するためにSFINAEのテクニックを使用することはできますが、不可解な問題を解決するために使用するべきではありません。ただ2つの異なる名前を使用してください。

+0

'vector'は早すぎる最適化のようです。ハスケルを模倣するためには 'list 'でなければなりません。 –

+0

@Maxim Egorushkin 'vector'と' list'は同形ですが、ベクトルは一般的にプロセッサ上で良く機能します。 C++は代数的データ型として 'list'を実装していないので、Haskellの' list'ほ​​ど安全ではありません。だから、デフォルトでそれを使っても何も買わないだろう。私はhaskellを模倣しようとしていない、私はそれから学んだ抽象を使用しようとしている。 – Polymer

関連する問題