「リフティング」とは何なのかわかりません。 「リフト」とは何かを理解する前に、まずモナドを理解する必要がありますか? (私もモナドについて完全に無知です:)または誰かが簡単な言葉で私にそれを説明できますか?
リフティングは数学的な概念というよりもデザインパターンです(ただし、ここの周りの人は、リフトがどのようにカテゴリであるかを示すことで私に反論することを期待しています)。
通常、パラメーターを持つデータ型があります。何かのようなもの
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 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にラップされた新しい関数を返します。これは(<*>)
などを使用して次の引数に適用できます。したがって、すべてのアリティにリフト機能を持たせる代わりに、デイジーチェーンのアプリカティブだけを用意します。
ポールとヤルチュの両方が良い説明です。
持ち上げる関数は任意の数の引数を持つことができ、同じ型である必要はないことを付け加えます。たとえば、liftFoo1を定義することもできます。
liftFoo1 :: (a -> b) -> Foo a -> Foo b
一般に、1つの引数を取る関数のリフティングは、タイプクラスFunctor
にキャプチャされ、リフティング操作はfmap
と呼ばれます。
fmap :: Functor f => (a -> b) -> f a -> f b
liftFoo1
のタイプとの類似性に注意してください。実際、liftFoo1
がある場合は、Foo
をFunctor
のインスタンスにできます。
instance Functor Foo where
fmap = liftFoo1
さらに、任意の数の引数にリフティングする一般化はapplicative styleと呼ばれます。固定数の引数を使用して関数のリフティングを把握するまで、これに飛び込むことはありません。しかし、あなたがそうするとき、 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
など)。
別の一般的なリフトは、Control.Monad.Trans
からのlift
です。 1つのモナドのモナドアクションを変換されたモナドのアクションに変換します。
一般に、「lift」lifts「ラップされた」タイプへの関数/アクション(したがって、元の関数は「ラップの下」で機能します)。
これとモナドなどを理解し、それらが有用である理由を理解する最良の方法は、おそらくコーディングして使用することです。以前にコード化したものがあれば、この恩恵を受けると思われる(つまり、コードが短くなるなど)場合は、試してみるだけで概念を簡単に把握できます。
リフティングは、関数を別の(通常はより一般的な)設定内の対応する関数に変換できる概念です。
この光沢のあるチュートリアル によれば、ファンクターはコンテナです(Maybe<a>
、List<a>
、またはTree<a>
など、別のタイプの要素を格納できるa
)。要素タイプa
にJavaジェネリック表記法、<a>
]を使用し、要素をツリー上の果実Tree<a>
と考えています。関数fmap
は、要素変換関数、a->b
およびコンテナfunctor<a>
を受け取ります。コンテナのすべての要素にa->b
を適用し、それをfunctor<b>
に効果的に変換します。最初の引数のみを指定すると、a->b
、fmap
はfunctor<a>
を待機します。つまり、a->b
のみを指定すると、この要素レベルの関数が関数functor<a> -> functor<b>
コンテナを操作します。これは、関数のliftingと呼ばれます。コンテナもaと呼ばれるためfunctor、モナドではなくファンクタがリフティングの前提条件です。モナドはリフティングに一種の「平行」です。どちらもファンクタの概念に依存し、f<a> -> f<b>
を実行します。そのリフティングは変換にa->b
を使用しますが、Monadではa -> f<b>
を定義する必要があります。