web-dev-qa-db-ja.com

Haskellの「リフティング」とは何ですか?

「リフティング」とは何なのかわかりません。 「リフト」とは何かを理解する前に、まずモナドを理解する必要がありますか? (私もモナドについて完全に無知です:)または誰かが簡単な言葉で私にそれを説明できますか?

127
GabiMe

リフティングは数学的な概念というよりもデザインパターンです(ただし、ここの周りの人は、リフトがどのようにカテゴリであるかを示すことで私に反論することを期待しています)。

通常、パラメーターを持つデータ型があります。何かのようなもの

data Foo a = Foo { ...stuff here ...}

Fooの多くの使用法は数値型(IntDoubleなど)を使用し、これらの数値をアンラップ、加算、または乗算するコードを記述し続ける必要があるとわかったとします、それらをラップしてバックアップします。 unwrap-and-wrapコードを1回記述することでこれを回避できます。この関数は、次のように見えるため、従来「リフト」と呼ばれています。

liftFoo2 :: (a -> b -> c) -> Foo a -> Foo b -> Foo c

つまり、2つの引数の関数((+)演算子など)を取り、それをFoosの同等の関数に変換する関数があります。

だから今、あなたは書くことができます

addFoo = liftFoo2 (+)

編集:詳細情報

もちろん、liftFoo3liftFoo4などを使用できます。しかし、これはしばしば必要ではありません。

観察から始める

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

同様に、liftAliftA3があります。しかし、別の演算子があるため、実際にはあまり使用しません

(<$>) = fmap

これにより、次のように記述できます。

result = myFunction <$> arg1 <*> arg2 <*> arg3 <*> arg4

myFunction <$> arg1という用語は、Fooにラップされた新しい関数を返します。これは(<*>)などを使用して次の引数に適用できます。したがって、すべてのアリティにリフト機能を持たせる代わりに、デイジーチェーンのアプリカティブだけを用意します。

168
Paul Johnson

ポールとヤルチュの両方が良い説明です。

持ち上げる関数は任意の数の引数を持つことができ、同じ型である必要はないことを付け加えます。たとえば、liftFoo1を定義することもできます。

liftFoo1 :: (a -> b) -> Foo a -> Foo b

一般に、1つの引数を取る関数のリフティングは、タイプクラスFunctorにキャプチャされ、リフティング操作はfmapと呼ばれます。

fmap :: Functor f => (a -> b) -> f a -> f b

liftFoo1のタイプとの類似性に注意してください。実際、liftFoo1がある場合は、FooFunctorのインスタンスにできます。

instance Functor Foo where
  fmap = liftFoo1

さらに、任意の数の引数にリフティングする一般化はapplicative styleと呼ばれます。固定数の引数を使用して関数のリフティングを把握するまで、これに飛び込むことはありません。しかし、あなたがそうするとき、 Haskellを学ぶ にはこれに関する良い章があります。 Typeclassopedia は、FunctorおよびApplicative(他の型クラスと同様に、その文書の右の章までスクロールします)。

お役に立てれば!

37
Martijn

例から始めましょう(より明確なプレゼンテーションのためにいくつかの空白が追加されます):

> 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「ラップされた」タイプへの関数/アクション(したがって、元の関数は「ラップの下」で機能します)。

これとモナドなどを理解し、それらが有用である理由を理解する最良の方法は、おそらくコーディングして使用することです。以前にコード化したものがあれば、この恩恵を受けると思われる(つまり、コードが短くなるなど)場合は、試してみるだけで概念を簡単に把握できます。

23
yairchu

リフティングは、関数を別の(通常はより一般的な)設定内の対応する関数に変換できる概念です。

http://haskell.org/haskellwiki/Lifting をご覧ください

12
Nasser Hadjloo

この光沢のあるチュートリアル によれば、ファンクターはコンテナです(Maybe<a>List<a>、またはTree<a>など、別のタイプの要素を格納できるa)。要素タイプaにJavaジェネリック表記法、<a>]を使用し、要素をツリー上の果実Tree<a>と考えています。関数fmapは、要素変換関数、a->bおよびコンテナfunctor<a>を受け取ります。コンテナのすべての要素にa->bを適用し、それをfunctor<b>に効果的に変換します。最初の引数のみを指定すると、a->bfmapfunctor<a>を待機します。つまり、a->bのみを指定すると、この要素レベルの関数が関数functor<a> -> functor<b>コンテナを操作します。これは、関数のliftingと呼ばれます。コンテナもaと呼ばれるためfunctor、モナドではなくファンクタがリフティングの前提条件です。モナドはリフティングに一種の「平行」です。どちらもファンクタの概念に依存し、f<a> -> f<b>を実行します。そのリフティングは変換にa->bを使用しますが、Monadではa -> f<b>を定義する必要があります。

0
Val