大まかな概要
関数型プログラミングでは、ファンクタは基本的に、通常の単項関数(つまり、1つの引数を持つ関数)を新しい型の変数間の関数に持ち上げる構造です。プレーンなオブジェクト間で単純な関数を記述および維持し、ファンクタを使用してそれらを持ち上げてから、複雑なコンテナオブジェクト間で関数を手動で記述する方がはるかに簡単です。さらなる利点は、単純な関数を1回だけ記述し、その後、異なるファンクタを介してそれらを再利用することです。
ファンクタの例には、配列、「たぶん」と「どちらか」のファンクタ、フューチャー(例:https : //github.com/Avaq/Flutureを参照)、およびその他多数が含まれます。
図
姓名から完全な人物の名前を構成する関数を考えてみます。fullName(firstName, lastName)
2つの引数の関数のように定義することもできますが、1つの引数の関数のみを扱うファンクタには適していません。修正するには、すべての引数を単一のオブジェクトname
に収集します。これは、関数の単一の引数になります。
// In JavaScript notation
fullName = name => name.firstName + ' ' + name.lastName
配列に多数の人がいる場合はどうでしょうか?手動でリストを調べる代わりに、短いコード1行の配列用に提供されfullName
ているmap
メソッドを介して関数を再利用できます。
fullNameList = nameList => nameList.map(fullName)
そしてそれを
nameList = [
{firstName: 'Steve', lastName: 'Jobs'},
{firstName: 'Bill', lastName: 'Gates'}
]
fullNames = fullNameList(nameList)
// => ['Steve Jobs', 'Bill Gates']
これは、私たちのすべてのエントリがとプロパティのnameList
両方firstName
を提供するオブジェクトである場合はいつでも機能しlastName
ます。しかし、いくつかのオブジェクトがそうでない場合(またはまったくオブジェクトでない場合)はどうなりますか?エラーを回避してコードをより安全にするために、オブジェクトをMaybe
タイプにラップすることができます(例:https : //sanctuary.js.org/#maybe-type):
// function to test name for validity
isValidName = name =>
(typeof name === 'object')
&& (typeof name.firstName === 'string')
&& (typeof name.lastName === 'string')
// wrap into the Maybe type
maybeName = name =>
isValidName(name) ? Just(name) : Nothing()
ここで、Just(name)
は有効な名前のみを保持するコンテナであり、Nothing()
他のすべてに使用される特別な値です。ここで、引数の有効性を確認するために中断(または忘れる)する代わりfullName
に、map
メソッドに基づいて、元の関数を別の1行のコードで再利用(リフト)できます。今回はMaybe型に提供されます。
// Maybe Object -> Maybe String
maybeFullName = maybeName => maybeName.map(fullName)
そしてそれを
justSteve = maybeName(
{firstName: 'Steve', lastName: 'Jobs'}
) // => Just({firstName: 'Steve', lastName: 'Jobs'})
notSteve = maybeName(
{lastName: 'SomeJobs'}
) // => Nothing()
steveFN = maybeFullName(justSteve)
// => Just('Steve Jobs')
notSteveFN = maybeFullName(notSteve)
// => Nothing()
カテゴリー理論
ファンクタで圏論は、その射の構図を尊重二つのカテゴリー間のマップです。コンピュータ言語、関心のメインカテゴリは、その一つであるオブジェクトであるタイプ(値の特定のセット)、及びその射な機能f:a->b
一つのタイプからa
別のタイプへb
。
たとえばa
、String
型、b
数値型であるとします。これf
は、文字列をその長さにマッピングする関数です。
// f :: String -> Number
f = str => str.length
ここでa = String
は、すべての文字列b = Number
のセットとすべての数値のセットを表します。その意味で、a
とb
は両方ともセットカテゴリのオブジェクトを表します(タイプのカテゴリに密接に関連しており、違いはここでは重要ではありません)。集合カテゴリでは、2つの集合間の射は、最初の集合から2番目の集合までのすべての関数です。したがって、f
ここでの長さ関数は、文字列のセットから数値のセットへの射です。
セットカテゴリのみを考慮するため、関連するファンクタはそれ自体から、オブジェクトにオブジェクトを送信し、射を射に変換するマップであり、特定の代数法則を満たします。
例: Array
Array
多くのことを意味する可能性がありますが、Functorは1つだけです。つまり、型構成、a
型[a]
のすべての配列の型への型のマッピングa
です。たとえば、Array
ファンクタは型String
を型[String]
(任意の長さの文字列のすべての配列のセット)にマップし、型をNumber
対応する型[Number]
(数値のすべての配列のセット)に設定します。
Functorマップを混同しないようにすることが重要です
Array :: a => [a]
射付きでa -> [a]
。ファンクタは、単純に型を型に型a
にマップ(関連付け)し[a]
ます。各タイプが実際には要素のセットであることは、ここでは関係ありません。対照的に、射はそれらのセット間の実際の関数です。たとえば、自然な射(関数)があります。
pure :: a -> [a]
pure = x => [x]
これは、値を1つのエントリとして1要素の配列に送信します。その関数はFunctorの一部ではありませんArray
!このファンクターの観点から見るとpure
、他の関数と同じように機能するだけであり、特別なものはありません。
一方、Array
Functorには2番目の部分、つまり射の部分があります。これは、射f :: a -> b
を射に写像し[f] :: [a] -> [b]
ます:
// a -> [a]
Array.map(f) = arr => arr.map(f)
ここでarr
型の値を有する任意の長さの任意の配列でありa
、そしてarr.map(f)
型の値と同じ長さの配列であるb
エントリ適用した結果である、f
のエントリにarr
。ファンクタにするには、アイデンティティをアイデンティティに、組成を組成にマッピングする数学的法則が成り立つ必要があります。これは、このArray
例では簡単に確認できます。