関数型プログラミングに関するさまざまな記事を読みながら、「ファンクター」という用語に何度か出会ったことがありますが、著者は通常、読者がその用語をすでに理解していると想定しています。 Webを見て回ると、過度に技術的な説明( Wikipediaの記事 を参照)または信じられないほどあいまいな説明(この ocaml-tutorial Webサイト のFunctorのセクションを参照)が提供されています。
誰かが親切に用語を定義し、その使用法を説明し、おそらくFunctorがどのように作成され使用されるかの例を提供できますか?
Edit:この用語の背後にある理論には興味がありますが、概念の実装と実用化よりも理論には興味がありません。
Edit 2:いくつかのクロスターミノロジーが進行しているように見えます:C++の関数オブジェクトではなく、関数型プログラミングのFunctorを具体的に参照しています。
「ファンクター」という言葉は、非常に一般的で非常に抽象的な数学の分野であるカテゴリー理論に由来しています。関数型言語の設計者は、少なくとも2つの異なる方法で借用しています。
MLファミリーの言語では、ファンクターはパラメーターとして1つ以上の他のモジュールを取るモジュールです。これは高度な機能と見なされており、ほとんどの初心者プログラマーはこれに苦労しています。
実装と実際の使用例として、バランスのとれたバイナリ検索ツリーの好みの形式を一度だけファンクタとして定義できます。これは、パラメータとして以下を提供するモジュールを取ります。
バイナリツリーで使用されるキーのタイプ
キーの全順序関数
これを行うと、同じバランスの取れたバイナリツリーの実装を永久に使用できます。 (ツリーに格納される値のタイプは通常多態のままです。ツリーは値をコピーする以外に値を見る必要はありませんが、ツリーはキーを比較できる必要があり、比較関数を取得します。ファンクターのパラメーター。)
MLファンクタのもう1つの用途は、 層状ネットワークプロトコルです 。リンクは、CMU Foxグループによる非常に素晴らしい論文へのリンクです。ファンクターを使用して、より単純なレイヤー(IPまたは直接イーサネット経由)のタイプでより複雑なプロトコルレイヤー(TCPなど)を構築する方法を示します。各レイヤーは、その下のレイヤーをパラメーターとして取るファンクターとして実装されます。ソフトウェアの構造は、プログラマーの心の中にのみ存在する層とは対照的に、実際に人々が問題について考える方法を反映しています。 1994年にこの作品が出版されたとき、それは大したことでした。
実際のMLファンクターの実際の例については、紙 ML Module Mania を参照してください。 MLモジュールシステムのわかりやすく明確な説明(他の種類のモジュールとの比較)については、Xavier Leroyの1994年のPOPLペーパー の最初の数ページを読んでください。 。
Haskellおよび関連する純粋な関数型言語では、Functor
はtype classです。型が特定の動作に特定の動作を提供する場合、型は型クラスに属します(より厳密には、型は型クラスの「インスタンス」です)。型T
は、特定のコレクションのような動作がある場合、クラスFunctor
に属することができます。
タイプT
は別のタイプでパラメーター化されます。これはコレクションの要素タイプと考える必要があります。完全なコレクションのタイプは、それぞれ整数、文字列、またはブール値を含む場合、T Int
、T String
、T Bool
のようなものになります。要素タイプが不明な場合、T a
のようにtype parametera
として書き込まれます。
例には、リスト(a
型のゼロ以上の要素)、Maybe
型(a
型のゼロまたは1つの要素)、a
、タイプa
の要素の配列、タイプa
の値を含むすべての種類の検索ツリー、および考えられる他の多くのツリー。
T
が満たさなければならないもう1つのプロパティは、a -> b
型(要素の関数)の関数がある場合、その関数を取得し、関連する関数を生成できる必要があることです。コレクション。これを行うには、fmap
型クラスのすべての型で共有される演算子Functor
を使用します。演算子は実際にはオーバーロードされているため、Int -> Bool
型のeven
関数がある場合、
fmap even
は、多くのすばらしいことを実行できるオーバーロード関数です。
整数のリストをブールのリストに変換します
整数のツリーをブールのツリーに変換します
Nothing
をNothing
に、Just 7
をJust False
に変換します
Haskellでは、このプロパティはfmap
のタイプを与えることで表現されます:
fmap :: (Functor t) => (a -> b) -> t a -> t b
ここに小さなt
があります。これは、「Functor
クラスの任意の型」を意味します。
簡単に言えば、Haskellではファンクターは一種のコレクションであり、要素の関数が与えられた場合、fmap
はコレクションの関数を返します。 。ご想像のとおり、これは広く再利用できるアイデアであるため、Haskellの標準ライブラリの一部として祝福されています。
いつものように、人々は新しい有用な抽象概念を発明し続けています。そして、あなたはapplicativeファンクタを検討したいと思うかもしれません。そのための最良のリファレンスは コナー・マクブライドとロス・パターソン。
ここでの他の答えは完全ですが、FP functorの使用の別の説明を試してみます。これを類推してください:
ファンクターは、タイプaのコンテナーであり、a→-bからマップする関数を適用すると、タイプ-のコンテナーを生成します- b。
C++で使用されるabstracted-function-pointerとは異なり、ここではファンクターはnot関数です。むしろ、関数の対象の場合に一貫して動作するものです。
3つの異なる意味があり、あまり関連していません!
Ocamlでは、パラメータ化されたモジュールです。 manual を参照してください。それらを理解する最良の方法は、例によると思います:(すばやく書かれ、バグがあるかもしれません)
module type Order = sig
type t
val compare: t -> t -> bool
end;;
module Integers = struct
type t = int
let compare x y = x > y
end;;
module ReverseOrder = functor (X: Order) -> struct
type t = X.t
let compare x y = X.compare y x
end;;
(* We can order reversely *)
module K = ReverseOrder (Integers);;
Integers.compare 3 4;; (* this is false *)
K.compare 3 4;; (* this is true *)
module LexicographicOrder = functor (X: Order) ->
functor (Y: Order) -> struct
type t = X.t * Y.t
let compare (a,b) (c,d) = if X.compare a c then true
else if X.compare c a then false
else Y.compare b d
end;;
(* compare lexicographically *)
module X = LexicographicOrder (Integers) (Integers);;
X.compare (2,3) (4,5);;
module LinearSearch = functor (X: Order) -> struct
type t = X.t array
let find x k = 0 (* some boring code *)
end;;
module BinarySearch = functor (X: Order) -> struct
type t = X.t array
let find x k = 0 (* some boring code *)
end;;
(* linear search over arrays of integers *)
module LS = LinearSearch (Integers);;
LS.find [|1;2;3] 2;;
(* binary search over arrays of pairs of integers,
sorted lexicographically *)
module BS = BinarySearch (LexicographicOrder (Integers) (Integers));;
BS.find [|(2,3);(4,5)|] (2,3);;
これで、多くの可能な注文、新しい注文を作成する方法、それらを簡単にバイナリ検索または線形検索することができます。汎用プログラミングFTW。
Haskellのような関数型プログラミング言語では、「マッピング」できるタイプコンストラクター(リスト、セットなどのパラメーター化されたタイプ)を意味します。正確には、ファンクターf
には(a -> b) -> (f a -> f b)
。これはカテゴリー理論に起源があります。リンクしたウィキペディアの記事は、この使用法です。
class Functor f where
fmap :: (a -> b) -> (f a -> f b)
instance Functor [] where -- lists are a functor
fmap = map
instance Functor Maybe where -- Maybe is option in Haskell
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
fmap (+1) [2,3,4] -- this is [3,4,5]
fmap (+1) (Just 5) -- this is Just 6
fmap (+1) Nothing -- this is Nothing
したがって、これは特別な種類の型コンストラクターであり、Ocamlのファンクターとはほとんど関係ありません!
OCamlでは、パラメーター化されたモジュールです。
C++を知っているなら、OCamlファンクターをテンプレートと考えてください。 C++にはクラステンプレートのみがあり、ファンクターはモジュールスケールで機能します。
ファンクターの例はMap.Makeです。 module StringMap = Map.Make (String);;
は、文字列キーマップで機能するマップモジュールを構築します。
ポリモーフィズムだけではStringMapのようなものを達成できませんでした。キーについていくつかの仮定を行う必要があります。 Stringモジュールには、完全に順序付けられた文字列型の操作(比較など)が含まれ、ファンクターはStringモジュールに含まれる操作に対してリンクします。オブジェクト指向プログラミングでも同様のことができますが、メソッドの間接的なオーバーヘッドが発生します。
あなたはかなり良い答えを得ました。私はピッチングします:
ファンクターは、数学的な意味で、代数上の特別な種類の関数です。代数を別の代数にマップする最小関数です。 「最小性」はファンクターの法則によって表現されます。
これを見るには2つの方法があります。たとえば、リストはある種のファンクターです。つまり、型 'a'上の代数が与えられると、型 'a'のものを含むリストの互換性のある代数を生成できます。 (例:要素を含むシングルトンリストへの要素を取得するマップ:f(a) = [a])再び、互換性の概念はファンクターの法則によって表されます。
一方、ファンクターfがタイプaを「オーバー」している場合(つまり、faはファンクターfをタイプaの代数に適用した結果)、gからの関数:a-> bを計算すると、 faをf bにマップする新しいファンクターF =(fmap g)。要するに、fmapは「ファンクターパーツ」を「ファンクターパーツ」にマップするFの一部であり、gは「代数パーツ」を「代数パーツ」にマップする関数の一部です。関数、ファンクター、そして完了すると、ISもファンクターです。
異なる言語がファンクターの異なる概念を使用しているように見えるかもしれませんが、そうではありません。彼らは単に異なる代数に対してファンクターを使用しているだけです。 OCamlsにはモジュールの代数があり、その代数に対するファンクタを使用すると、「互換性のある」方法でモジュールに新しい宣言を添付できます。
Haskellファンクターは型クラスではありません。型クラスを満たす自由変数を持つデータ型です。データ型の内臓(自由変数なし)を掘り下げたい場合は、基礎となる代数のファンクターとしてデータ型を再解釈できます。例えば:
データF = F Int
intsのクラスと同型です。したがって、値コンストラクタとしてのFは、Intを等価代数であるF Intにマップする関数です。ファンクターです。一方、ここではfmapを無料で入手できません。それがパターンマッチングの目的です。
ファンクターは、代数的に互換性のある方法で、代数の要素に「アタッチ」するのに適しています。
その質問に対する最良の答えは、ブレント・ヨーギーによる「Typeclassopedia」にあります。
モナドリーダーのこの号には、ファンクターとは何かの正確な定義と、他の概念やダイアグラムの多くの定義が含まれています。 (Monoid、Applicative、Monad、およびその他の概念は、ファンクターに関連して説明および表示されます)。
http://haskell.org/sitewiki/images/8/85/TMR-Issue13.pdf
typeclassopedia for Functorからの抜粋:「単純な直感は、Functorが何らかの種類の「コンテナ」を表し、コンテナ内のすべての要素に関数を均一に適用する機能があることです」
しかし、実際にはtypeclassopedia全体が非常に推奨される読み物であり、驚くほど簡単です。ある方法で、そこに提示されたタイプクラスは、与えられた振る舞いや能力の語彙を提供するという意味で、オブジェクトのデザインパターンに類似していることがわかります。
乾杯
InriaのWebサイトにあるO'Reilly OCamlの本には、かなり良い例があります(これを書いている時点では残念ながらダウンしています)。 caltechが使用するこの本で、非常によく似た例を見つけました: OCamlの紹介(pdfリンク) 。関連するセクションは、ファンクターの章です(本の139ページ、PDFの149ページ)。
本には、リストで構成されるデータ構造を作成するMakeSetというファンクタがあり、要素を追加し、要素がリスト内にあるかどうかを判断し、要素を見つける機能があります。セット内にあるかどうかを判断するために使用される比較関数は、パラメーター化されています(これにより、MakeSetはモジュールではなくファンクターになります)。
また、大文字と小文字を区別しない文字列比較を行うために、比較関数を実装するモジュールもあります。
ファンクターと比較を実装するモジュールを使用して、新しいモジュールを1行で作成できます。
module SSet = MakeSet(StringCaseEqual);;
大文字と小文字を区別しない比較を使用するセットデータ構造のモジュールを作成します。大文字と小文字を区別する比較を使用するセットを作成する場合は、新しいデータ構造モジュールではなく、新しい比較モジュールを実装するだけで済みます。
東部はファンクターをC++のテンプレートと比較しました。
プログラミングPOVからのファンクターに関する記事 に続いて、より具体的に プログラミング言語でどのように表示されるか です。
ファンクタの実用的な使用法はモナドであり、それを探すとモナドに関する多くのチュートリアルを見つけることができます。
他の回答と、今から投稿する内容を考えると、かなり過負荷のWordですが、とにかく...
Haskellの「ファンクター」という言葉の意味に関するヒントについては、GHCiに問い合わせてください。
_Prelude> :info Functor
class Functor f where
fmap :: forall a b. (a -> b) -> f a -> f b
(GHC.Base.<$) :: forall a b. a -> f b -> f a
-- Defined in GHC.Base
instance Functor Maybe -- Defined in Data.Maybe
instance Functor [] -- Defined in GHC.Base
instance Functor IO -- Defined in GHC.Base
_
そのため、基本的に、Haskellのファンクターはマッピングできるものです。別の言い方をすれば、ファンクターは、特定の関数を使用して、含まれる値を変換するように要求できるコンテナーと見なすことができるものです。したがって、リストの場合、fmap
はmap
と一致し、Maybe
、fmap f (Just x) = Just (f x)
、_fmap f Nothing = Nothing
_などと一致します。
The Functor typeclass サブセクションと Functors、Applicative Functors and Monoids of Learn Good a Haskell for Great Good この特定の場所の例を示しますコンセプトは便利です。 (要約:たくさんの場所!:-))
モナドはファンクターとして扱うことができ、実際、Craig Stuntzが指摘しているように、最も頻繁に使用されるファンクターはモナドである傾向があります... OTOH、時にはFunctorタイプクラスのインスタンスを作成すると便利ですそれをモナドにする手間をかけずに。 (例:_Control.Applicative
_からのZipList
の場合、 前述のページの1つ で言及されています。)
トップ投票の answer 、ユーザー Wei H へのコメント:
私はMLファンクターとHaskellファンクターの両方を理解していますが、それらを相互に関連付ける洞察が欠けています。カテゴリ理論的な意味で、これら2つの関係はどうですか?
注:私はMLを知らないので、関連する間違いは許してください。
最初に、「カテゴリ」と「ファンクター」の定義にすべて精通していると仮定しましょう。
簡潔な答えは、「Haskell-functors」は(endo-)functors F : Hask -> Hask
「MLファンクター」はファンクターであるG : ML -> ML'
。
ここで、Hask
はHaskellの型とそれらの間の関数によって形成されるカテゴリであり、同様にML
とML'
は、ML構造によって定義されたカテゴリです。
注:Hask
をカテゴリにすると 技術的な問題 がありますが、それらを回避する方法があります。
カテゴリ理論の観点から、これはHask
- functorがHaskell型のマップF
であることを意味します:
data F a = ...
haskell関数のマップfmap
とともに:
instance Functor F where
fmap f = ...
MLはほぼ同じですが、標準的なfmap
抽象化はありませんが、定義してみましょう。
signature FUNCTOR = sig
type 'a f
val fmap: 'a -> 'b -> 'a f -> 'b f
end
つまり、f
はML
- typesをマップし、fmap
はML
- functionsをマップします。
functor StructB (StructA : SigA) :> FUNCTOR =
struct
fmap g = ...
...
end
ファンクタF: StructA -> StructB
。
「ファンクターは、カテゴリーの構成とアイデンティティを保持するオブジェクトと射のマッピングです。」
カテゴリーとは何かを定義しましょうか?
それはたくさんのオブジェクトです!
円の中にいくつかの点を描いて(今は2つの点、もう1つは「a」、もう1つは「b」)、円に名前を付けますA(Category)今のところ。
カテゴリーは何を保持しますか?
すべてのオブジェクトのオブジェクトとアイデンティティ関数間の構成。
そのため、Functorを適用した後、オブジェクトをマップし、構成を保持する必要があります。
「A」はオブジェクト['a'、 'b']を持つカテゴリであり、射a-> bが存在すると想像してみましょう。
ここで、これらのオブジェクトとモーフィズムを別のカテゴリ「B」にマッピングできるファンクターを定義する必要があります。
ファンクターが「Maybe」と呼ばれているとしましょう
data Maybe a = Nothing | Just a
したがって、カテゴリ「B」は次のようになります。
別の円を描いてください。ただし、今回は「a」と「b」の代わりに「Maybe a」と「Maybe b」を使用します。
すべてが良いようで、すべてのオブジェクトがマッピングされます
「a」は「Maybe a」に、「b」は「Maybe b」になりました。
しかし問題は、射を「a」から「b」にマッピングする必要があることです。
つまり、「A」の射a-> bは、「多a」->「多b」の射に写像されるはずです。
a-> bからの射はfと呼ばれ、次に 'Maybe a'-> 'Maybe b'からの射は 'fmap f'と呼ばれます
ここで、「f」が「A」で実行していた機能を確認し、「B」でそれを複製できるかどうかを確認します。
「A」の「f」の関数定義:
f :: a -> b
fはaを取り、bを返します
「B」の「f」の関数定義:
f :: Maybe a -> Maybe b
fはMaybe aを取り、Maybe bを返す
fmapを使用して、関数「f」を「A」から「B」の関数「fmap f」にマッピングする方法を見てみましょう。
fmapの定義
fmap :: (a -> b) -> (Maybe a -> Maybe b)
fmap f Nothing = Nothing
fmap f (Just x) = Just(f x)
それで、私たちはここで何をしていますか?
タイプ「a」の「x」に関数「f」を適用しています。 「Nothing」の特別なパターンマッチングは、Functor Maybe
。
そのため、オブジェクト[a、b]と射[f]をカテゴリ 'A'からカテゴリ 'B'にマッピングしました。
それはファンクターです!
以前の理論的または数学的な答えと矛盾しないように、Functorは1つのメソッドのみを持ち、関数として効果的に使用される(オブジェクト指向プログラミング言語の)オブジェクトでもあります。
たとえば、JavaのRunnableインターフェイスには、runという1つのメソッドしかありません。
最初にJavascriptのこの例を考えてください。これには一流の関数があります。
[1, 2, 5, 10].map(function(x) { return x*x; });
出力:[1、4、25、100]
Mapメソッドは関数を取り、元の配列の同じ位置にある値にその関数を適用した結果である各要素を持つ新しい配列を返します。
同じことを行うには、JavaでFunctorを使用する場合、最初にインターフェースを定義する必要があります。
public interface IntMapFunction {
public int f(int x);
}
次に、マップ関数を持つコレクションクラスを追加すると、次のことができます。
myCollection.map(new IntMapFunction() { public int f(int x) { return x * x; } });
これは、IntMapFunctionのインラインサブクラスを使用してFunctorを作成します。これは、以前のJavaScriptの例の関数に相当するOOと同等です。
Functorを使用すると、関数テクニックをOO言語で使用できます。もちろん、一部のOO言語は関数を直接サポートしているため、これは必要ありません。
JavaScriptの配列はマップを実装するため、ファンクターです。 Promise、Streams、およびTreesは、多くの場合、関数型言語でマップを実装します。実装する場合、それらはファンクターと見なされます。ファンクターのmapメソッドは、独自のコンテンツを取得し、mapに渡された変換コールバックを使用してそれぞれを変換し、最初のファンクターとして構造を含むが、変換された値を持つ新しいファンクターを返します。
src: https://www.youtube.com/watch?v=DisD9ftUyCk&feature=youtu.be&t=76