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

興味深いことに、「 ファンクター 」という用語は、プログラミング言語ごとにまったく異なるものを意味します。 たとえば、 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

終わり ;


プラグインのインターフェース(署名)を定義したLoudPlugin



SilentPlugin



LoudPlugin



などの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



コンストラクターで定義されます。 つまり、 Functor



fmap



関数を実装するものであり、関数(タイプ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



fmap



v



適用されたg



からのLeaf



であると言います。 そして、左ブランチl



と右r



Node



上の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の一部が間違っていると言います。



All Articles