回答:
リフトは、数学的概念よりもデザインパターンに近いものです(ただし、このあたりの誰かが、リフトがカテゴリであるかどうかを示すことで、私が反論することを期待しています)。
通常、パラメーター付きのデータ型があります。何かのようなもの
data Foo a = Foo { ...stuff here ...}
Foo
数値型を取る(など)の多くの用途がありInt
、Double
これらの数値をアンラップし、それらを加算または乗算してからラップアップするコードを記述する必要があるとします。unwrap-and-wrapコードを1回記述することで、これを短絡できます。この関数は、次のようになるため、従来「リフト」と呼ばれています。
liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c
つまり、2つの引数を持つ関数((+)
演算子など)を取り、それをFoosの同等の関数に変換する関数があります。
だから今あなたは書くことができます
addFoo = liftFoo2 (+)
編集:詳細
もちろんliftFoo3
、liftFoo4
なども可能です。ただし、これは多くの場合必要ありません。
観察から始める
liftFoo1 :: (a -> b) -> Foo a -> Foo b
しかし、それはとまったく同じfmap
です。だからliftFoo1
あなたが書くよりも
instance Functor Foo where
fmap f foo = ...
完全な規則性が本当に必要な場合は、次のように言うことができます
liftFoo1 = fmap
Foo
ファンクタにすることができれば、おそらくそれをアプリケーションファンクタにすることができます。実際、記述できる場合liftFoo2
、アプリケーションインスタンスは次のようになります。
import Control.Applicative
instance Applicative Foo where
pure x = Foo $ ... -- Wrap 'x' inside a Foo.
(<*>) = liftFoo2 ($)
(<*>)
Foo の演算子には次のタイプがあります
(<*>) :: Foo (a -> b) -> Foo a -> Foo b
ラップされた値にラップされた関数を適用します。したがって、実装できるliftFoo2
場合は、この点でこれを書くことができます。またはliftFoo2
、Control.Applicative
モジュールに含まれているため、
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
同様にとがliftA
ありliftA3
ます。ただし、別の演算子があるため、実際にはあまり使用しません。
(<$>) = fmap
これにより、次のように記述できます。
result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4
この用語myFunction <$> arg1
は、Fooでラップされた新しい関数を返します。これは(<*>)
、を使用して次の引数に適用できます。したがって、すべてのアリティに対してリフト機能を使用する代わりに、アプリケーションのデイジーチェーンを使用できます。
lift id == id
とを尊重する必要があることを思い出してくださいlift (f . g) == (lift f) . (lift g)
。
id
と.
アイデンティティの矢印であり、それぞれ、いくつかのカテゴリの構図を矢印します。ハスケルの話すとき通常、問題のカテゴリは、その矢印Haskellの関数です(つまり、「Hask」、であるid
と.
Haskellのあなたが知っている機能と愛を参照してください)。
instance Functor Foo
、ではなくinstance Foo Functor
、と読む必要がありますか?自分で編集しますが、100%確実ではありません。
Paulとyairchuはどちらも良い説明です。
取り上げる関数は任意の数の引数を持つことができ、それらは同じ型である必要はないことを付け加えたいと思います。たとえば、liftFoo1を定義することもできます。
liftFoo1 :: (a -> b) -> Foo a -> Foo b
一般に、1つの引数を取る関数のリフティングは、型class Functor
でキャプチャされ、リフティング操作が呼び出されfmap
ます。
fmap :: Functor f => (a -> b) -> f a -> f b
liftFoo1
のタイプとの類似性に注意してください。実際、がある場合liftFoo1
、次Foo
のインスタンスを作成できますFunctor
。
instance Functor Foo where
fmap = liftFoo1
さらに、任意の数の引数に持ち上げる一般化は、適用スタイルと呼ばれます。引数の数が決まっている関数の持ち上げ方を理解するまでは、これに飛び込む必要はありません。しかし、そうするとき、Haskellがこれについて良い章を持っていることを学びましょう。Typeclassopediaは説明別の良い文書でのFunctorとのApplicative(だけでなく、他のタイプのクラスを、スクロール、その文書の右章まで)。
お役に立てれば!
例から始めましょう(見やすくするために空白がいくつか追加されています)。
> import Control.Applicative
> replicate 3 'a'
"aaa"
> :t replicate
replicate :: Int -> b -> [b]
> :t liftA2
liftA2 :: (Applicative f) => (a -> b -> c) -> (f a -> f b -> f c)
> :t liftA2 replicate
liftA2 replicate :: (Applicative f) => f Int -> f b -> f [b]
> (liftA2 replicate) [1,2,3] ['a','b','c']
["a","b","c","aa","bb","cc","aaa","bbb","ccc"]
> ['a','b','c']
"abc"
liftA2
プレーンタイプの関数を、リストなどのでラップされた同じタイプのApplicative
関数に変換しますIO
。
別の一般的なリフトはlift
からControl.Monad.Trans
です。1つのモナドのモナドアクションを変換されたモナドのアクションに変換します。
一般に、「リフト」は関数/アクションを「ラップ」タイプに持ち上げます(したがって、元の関数は「ラップの下」で機能します)。
これとモナドなどを理解し、それらが有用である理由を理解するための最良の方法は、おそらくコード化して使用することです。これまでにコーディングしたことでメリットがあると思われるものがあれば(つまり、コードが短くなるなど)、実際に試してみれば、簡単に概念を把握できます。
リフティングは、関数を別の(通常はより一般的な)設定内の対応する関数に変換できるようにする概念です。
この光沢のあるチュートリアルによれば、ファンクターは(または、別のタイプの要素を格納できるようなMaybe<a>
)コンテナーです。要素タイプにJavaジェネリック表記を使用し、要素をツリー上のベリーと考えています。機能がある要素の変換機能、取り、そしてコンテナが。これは、コンテナのすべての要素に適用され、効果的にに変換されます。最初の引数のみを指定すると、はを待ちます。つまり、単独で指定すると、この要素レベルの関数がコンテナで動作する関数に変わります。これはリフティングと呼ばれますList<a>
Tree<a>
a
<a>
a
Tree<a>
fmap
a->b
functor<a>
a->b
functor<b>
a->b
fmap
functor<a>
a->b
functor<a> -> functor<b>
関数の。コンテナはファンクタとも呼ばれるため、持ち上げにはモナドではなくファンクタが必要です。モナドはリフティングに「平行」のようなものです。どちらもFunctorの概念に依存していますf<a> -> f<b>
。違いは、リフトはa->b
変換に使用するのに対して、モナドはユーザーがを定義する必要があるということa -> f<b>
です。
r
からタイプ(c
さまざまな目的で使用しましょう)はファンクターです。それらは何も「含まない」c
。この例では、fmapは関数構成であり、a -> b
関数とr -> a
1を使用して、新しいr -> b
関数を提供します。それでもコンテナはありません。また、可能であれば、最終文のために再度マークダウンします。
fmap
関数であり、何も「待機」しません。Functorである「コンテナ」は、持ち上げる全体のポイントです。また、モナドは、どちらかと言えば、持ち上げの2つのアイデアです。モナドは、持ち上げが1回だけであるかのように、いくつかの正の回数持ち上げられたものを使用できるようにします。これはフラット化と呼ばれます。
To wait
、to expect
、to anticipate
同義語です。「関数の待機」とは、「関数の予測」を意味します。
b = 5 : a
、およびf 0 = 55
f n = g n
、両方の「コンテナ」を疑似変異伴います。また、リストは通常、完全にメモリに保存されますが、関数は通常、計算として保存されます。しかし、呼び出しと呼び出しの間に保存されていないメモ/一元的なリストはどちらも、その考えからがらくたを壊します。