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