プログラミング言語のファンクター

興味深いことに、「 ファンクター 」という用語は、プログラミング言語ごとにまったく異なるものを意味します。 たとえば、 C ++を考えます。 C ++のスキルを習得したすべての人は、 operator()を実装するクラスがファンクターと呼ばれることを知っています。 次に、 標準MLを使用します。 MLでは、ファンクターは構造体を構造体にマップします。 今Haskell 。 Haskellでは、ファンクターはカテゴリーに対する準同型です。 Prologでは、ファンクターとは構造の先頭にある原子を意味します。 それらはすべて異なります。 それぞれを詳しく見てみましょう。

C ++のファンクター


C ++のファンクターは、「 機能オブジェクト 」の略です 。 機能オブジェクトは、 operator()定義されているC ++クラスのインスタンスです。 C ++クラスにoperator()を定義すると、関数として機能するオブジェクトを取得できますが、状態を保持することもできます。 例えば

 #include <iostream> #include <string> class SimpleFunctor { std::string name_; public: SimpleFunctor(const char *name) : name_(name) {} void operator()() { std::cout << "Oh, hello, " << name_ << endl; } }; int main() { SimpleFunctor sf("catonmat"); sf(); //  "Oh, hello, catonmat" } 

sfはオブジェクトですが、 main関数でsf()を呼び出すことができることに注意してください。 これは、 SimpleFunctorクラスでoperator()定義されているためです。

ほとんどの場合、C ++のファンクターは、STLアルゴリズムの述部、疑似クロージャー、または比較関数として使用されます。 別の例を示します。 整数のリストがあり、すべての偶数の合計とすべての奇数の合計を検索するとします。 functorおよびfor_each最適です。

 #include <algorithm> #include <iostream> #include <list> class EvenOddFunctor { int even_; int odd_; public: EvenOddFunctor() : even_(0), odd_(0) {} void operator()(int x) { if (x%2 == 0) even_ += x; else odd_ += x; } int even_sum() const { return even_; } int odd_sum() const { return odd_; } }; int main() { EvenOddFunctor evenodd; int my_list[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; evenodd = std::for_each(my_list, my_list+sizeof(my_list)/sizeof(my_list[0]), evenodd); std::cout << " : " << evenodd.even_sum() << "\n"; std::cout << " : " << evenodd.odd_sum() << std::endl; // : //  : 30 //  : 25 } 

ここでは、 EvenOddFunctorインスタンスEvenOddFunctor for_each渡されます。 for_eachは、my_listの各アイテムを反復処理し、ファンクターを呼び出します。 その後、偶数要素と奇数要素の合計を含むevenoddファンクタのコピーを返します。

Standart MLのファンクター


OOPの観点から定式化することは困難です。MLのファンクターは、インターフェイスの一般的な実装です。 MLの観点では、ファンクターはMLモジュールシステムの一部であり、構造を構成できます。

たとえば、プラグインシステムを作成し、すべてのプラグインが必要なインターフェイスを実装するようにしたいとします。これには、簡単にするために、 perform関数のみが含まれます。 MLでは、最初にプラグインの署名を設定する必要があります。

署名プラグイン=
sig
val perform unit- > unit
終わり ;

プラグインのインターフェース(署名)を定義したLoudPluginSilentPluginLoudPluginなどの2つのプラグインを実装できます。 実装は構造を介して行われ、

構造 LoudPlugin :>プラグイン=
構造
fun perform = print "RUNNING THE JOB LOUD!\ n"
終わり ;

SilentPlugin、

構造 SilentPlugin :>プラグイン=
構造
fun perform = print "静かにジョブを実行\ n"
終わり ;

これでファンクターに近づきました。 MLのファンクターは構造体を引数として取るため、 Pluginは引数として必要であると記述できます。

ファンクターパフォーマー P プラグイン =
構造
楽しい仕事 = P 実行
終わり ;

このファンクターはPの引数としてPluginを取り、それをjob関数に使用します。 job関数はPプラグインのperform関数を呼び出します。
それでは、 Performerファンクタを使用してみましょう。 ファンクターは構造体を返すことを覚えておいてください。

構造 LoudPerformer = Performer LoudPlugin ;
構造 SilentPerformer = Performer SilentPlugin ;

LoudPerformer 仕事 ;
サイレントパフォーマンス 仕事 ;

それは推論されます

 タスクを大々的に実行します!
静かにタスクを実行する 

これはおそらく、標準MLファンクターの最も単純な例です。

Haskellのファンクター


Haskellファンクターは、本当のファンクターであるべきです。 Haskellファンクターは、カテゴリー理論の数学的ファンクターを非常に連想させます。 カテゴリ理論では、ファンクタFはカテゴリ間のマッピングであり、カテゴリの構造は保持されます。つまり、2つのカテゴリ間の準同型です。

Haskellでは、この定義は単純な型クラスとして実装され、

クラス Functor f ここで
fmap : :(a- > b -> f a- > f b

たとえば、MLを振り返ると、Haskellの型クラスは署名に似ていますが、型に対して定義されている点が異なります。 特定のクラスのインスタンスになるために、型が実装しなければならない操作を定義します。 ただし、この場合、 Functor型ではなく、型fコンストラクターで定義されます。 つまり、 Functorfmap関数を実装するものであり、関数(タイプaを受け入れaタイプbを返す)とタイプfa (タイプf適用されるタイプfコンストラクターから構築されたタイプ)の値を取り、タイプfb値を返します。

彼が何をしているのかを理解するには、 fmapをコンテナ内の各要素に操作を適用する関数と考えてください。

ファンクタの最も簡単な例は、通常のリストと、リスト内の各要素に関数を適用するmap関数です。

プレリュード>マップ(+1)[1,2,3,4,5]
[2,3,4,5,6]

この単純な例では、 Functor関数fmapは単なるmapあり、 f型のコンストラクターは[] -リスト型コンストラクターです。 したがって、たとえばリストのFunctorは次のように定義されます。

インスタンス Functor [ ] ここで
fmap = マップ

mapではなくfmapを使用して、これが本当に正しいことを見てみましょう。

プレリュード> fmap(+1)[1,2,3,4,5]
[2,3,4,5,6]

しかし、Functorの定義は構造の維持について何も述べていないことに注意してください! したがって、通常のファンクターは、数学的なファンクターの定義の一部であるファンクターの法則を暗黙的に満たす必要があります。 2つのfmapルールがあります。

fmap id = id
fmap(g。h)= fmap g。 fmap h

最初のルールは、コンテナ内の各要素にアイデンティティ関数をマッピングしても効果がないことを示しています。 2番目のルールは、コンテナ内の各要素の上にある2つの関数の構成が、最初の関数の表示と同じであり、2番目の関数の表示と同じであることを示しています。

それらを最も鮮明に示すファンクターの別の例は、木の操作です。 ツリーをコンテナと考え、 fmap関数をツリー値に適用して、ツリー構造を保持します。

最初にツリーを定義しましょう。

データ Tree a = Node Tree a Tree a
|
派生 ショー

この定義は、 Treeタイプが2つのTree Node (ブランチ)(左右のブランチ)またはLeaf (リーフ)のいずれかであることを示しています。 deriving Show式により、show関数を使用してツリーを検査できます。

これで、ツリーツリー上にFunctorを定義できます。

インスタンス Functor Tree ここで
fmap g 葉v = g v
fmap g Node l r = Node fmap g l fmap g r

この定義は、値v Leaf上の関数g fmapv適用されたgからのLeafであると言います。 そして、左ブランチlと右rNode上のgからのfmapは、左右のブランチの値に適用されたfmapからのNodeです。

それでは、 fmapどのようにツリーで機能するかを説明しましょう。 String葉でツリーを構築し、それらの上の長さ関数を使用して各シートの長さを見つけましょう。

Prelude> let tree =(Node(Node(Leaf "hello")(Leaf "foo"))(Leaf "baar"))
Prelude> fmap length tree
ノード(ノード(リーフ5)(リーフ3))(リーフ4)

ここで、次のツリーを構築しました。

  *
           / \
          / \
         *「baar」
        / \
       / \
      / \
     / \
  「こんにちは」「foo」 

そしてその上にlength表示すると

  *
           / \
          / \
         * 4
        / \     
       / \
      / \
     / \
    5 3 

fmapが何をするかを示すもう1つの方法は、「通常の世界」から「 f世界」への関数を表示することです (元の場合はraise )。

実際、モナド、適用ファンクター、および矢印はすべてファンクターであるため、ファンクターはHaskellの基本型クラスです。 Haskellは、ファンクターが始まるところから始まると思います。

Haskellのタイプクラスの詳細については、 Typeclassopediaの優れた記事(17ページ以降)から始めてください。

プロローグのファンクター


そして最後に、Prologのファンクター。 Prologのファンクターはそれらの中で最も単純です。 2つの例が考えられます。 最初は、構造の先頭にある原子です。 たとえば、式を取ります

- 好き メアリーピザ

likes最初のアトムはファンクターです。

2番目はfunctorと呼ばれる組み込みの述語です。 アリティと構造ファンクターを返します。 例えば

- ファンクター 好き メアリーピザ ファンクター アリティ
ファンクター =いい
アリティ= 2

ここに、Prologにファンクターがあります。

おわりに


この記事では、「ファンクター」などの単純な用語が、プログラミング言語ごとにまったく異なるものを指すことを示しています。 したがって、ファンクターという用語を聞くとき、それが使用されているコンテキストを知ることが重要です。

元の記事: www.catonmat.net/blog/on-functors

そして、ここでそのようなこと...私はここで、それが判明した、私が記事を翻訳した人はハブにいると言われました=)
彼らはまた、記事のPrologの一部が間違っていると言います。

Source: https://habr.com/ru/post/J125995/


All Articles