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


138

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


9
多分役に立つかもしれませんが、そうでないかもしれません:haskell.org/haskellwiki/Lifting
kennytm

回答:


179

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

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

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 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場合は、この点でこれを書くことができます。またはliftFoo2Control.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でラップされた新しい関数を返します。これは(<*>)、を使用して次の引数に適用できます。したがって、すべてのアリティに対してリフト機能を使用する代わりに、アプリケーションのデイジーチェーンを使用できます。


26
リフトは標準の法律lift id == idとを尊重する必要があることを思い出してくださいlift (f . g) == (lift f) . (lift g)
Carlos Scheidegger、2013

13
リフトは確かに「カテゴリーか何か」です。カルロスはちょうど、のFunctor法律を記載されている場所をしているid.アイデンティティの矢印であり、それぞれ、いくつかのカテゴリの構図を矢印します。ハスケルの話すとき通常、問題のカテゴリは、その矢印Haskellの関数です(つまり、「Hask」、であるid.Haskellのあなたが知っている機能と愛を参照してください)。
Dan Burton

3
これはinstance Functor Foo、ではなくinstance Foo Functor、と読む必要がありますか?自分で編集しますが、100%確実ではありません。
アマロイ2014年

2
Applicativeなしで解除することは= Functorです。つまり、FunctorまたはApplicative Functorの2つの選択肢があります。1つ目は単一のパラメータ関数を持ち上げ、2つ目は複数のパラメータ関数を持ち上げます。ほぼそれだけです。正しい?それはロケット科学ではありません:)それはちょうどそのように聞こえます。素晴らしい回答をありがとうございます!
jhegedus

2
@atc:これは部分的なアプリケーションです。wiki.haskell.org/Partial_applicationを
Paul Johnson

41

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(だけでなく、他のタイプのクラスを、スクロール、その文書の右章まで)。

お役に立てれば!


25

例から始めましょう(見やすくするために空白がいくつか追加されています)。

> 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つのモナドのモナドアクションを変換されたモナドのアクションに変換します。

一般に、「リフト」は関数/アクションを「ラップ」タイプに持ち上げます(したがって、元の関数は「ラップの下」で機能します)。

これとモナドなどを理解し、それらが有用である理由を理解するための最良の方法は、おそらくコード化して使用することです。これまでにコーディングしたことでメリットがあると思われるものがあれば(つまり、コードが短くなるなど)、実際に試してみれば、簡単に概念を把握できます。


13

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

http://haskell.org/haskellwiki/Liftingを見てください


40
ええ、でもそのページは「私たちは通常(共変)ファンクターから始めます...」と始まります。初心者向けではありません。
ポールジョンソン

3
しかし「functor」はリンクされているので、初心者はそれをクリックするだけでFunctorが何であるかを確認できます。確かに、リンクされたページはそれほど良くありません。アカウントを取得して修正する必要があります。
jrockway

10
これは、他の関数型プログラミングサイトで見た問題です。初心者が一周するまで(そして曲がり角を曲がるまで)、各概念は他の(馴染みのない)概念の観点から説明されます。再帰を好むことと関係があるに違いありません。
DNA

2
このリンクに投票してください。リフトは、ある世界と別の世界を結びつけます。
eccstartup 2013

3
このような答えは、トピックをすでに理解している場合にのみ有効です。
doubleOrt

-2

この光沢のあるチュートリアルによれば、ファンクターは(または、別のタイプの要素を格納できるようなMaybe<a>)コンテナーです。要素タイプにJavaジェネリック表記を使用し、要素をツリー上のベリーと考えています。機能がある要素の変換機能、取り、そしてコンテナが。これは、コンテナのすべての要素に適用され、効果的にに変換されます。最初の引数のみを指定すると、はを待ちます。つまり、単独で指定すると、この要素レベルの関数がコンテナで動作する関数に変わります。これはリフティングと呼ばれますList<a>Tree<a>a<a>aTree<a>fmapa->bfunctor<a>a->bfunctor<b>a->bfmapfunctor<a>a->bfunctor<a> -> functor<b>関数の。コンテナはファンクタとも呼ばれるため、持ち上げにはモナドではなくファンクタが必要です。モナドはリフティングに「平行」のようなものです。どちらもFunctorの概念に依存していますf<a> -> f<b>。違いは、リフトはa->b変換に使用するのに対して、モナドはユーザーがを定義する必要があるということa -> f<b>です。


5
「ファンクターはコンテナ」はトロール風味のフレームベイトなので、値下げしました。例:関数の一部rからタイプ(cさまざまな目的で使用しましょう)はファンクターです。それらは何も「含まない」c。この例では、fmapは関数構成であり、a -> b関数とr -> a1を使用して、新しいr -> b関数を提供します。それでもコンテナはありません。また、可能であれば、最終文のために再度マークダウンします。
BMeph 2013

1
また、fmap関数であり、何も「待機」しません。Functorである「コンテナ」は持ち上げる全体のポイントです。また、モナドは、どちらかと言えば、持ち上げの2つのアイデアです。モナドは、持ち上げが1回だけであるかのように、いくつかの正の回数持ち上げられたものを使用できるようにします。これはフラット化と呼ばれます。
BMeph 2013

1
@BMeph To waitto expectto anticipate同義語です。「関数の待機」とは、「関数の予測」を意味します。
Val

@BMephファンクタはコンテナであるという考えの反例として関数を考えるのではなく、関数の正真正銘のファンクタインスタンスは、関数がコンテナではないという考えの反例であると考える必要があります。関数はドメインからコドメインへのマッピングであり、ドメインはすべてのパラメーターのクロス積であり、コドメインは関数の出力タイプです。同様に、リストは、Naturalsからリストの内部タイプへのマッピングです(ドメイン->コドメイン)。関数をメモしたり、リストを保持しなかったりすると、さらに似たものになります。
セミコロン

@BMephの唯一の理由リストの1つは、コンテナーに似ていると考えられています。多くの言語では、リストを変更できるのに対し、従来の関数では変更できません。:しかし、Haskellでも、それはどちらも突然変異させることができるよう公正な文はなく、両方のコピーが変異することができb = 5 : a、およびf 0 = 55 f n = g n、両方の「コンテナ」を疑似変異伴います。また、リストは通常​​、完全にメモリに保存されますが、関数は通常、計算として保存されます。しかし、呼び出しと呼び出しの間に保存されていないメモ/一元的なリストはどちらも、その考えからがらくたを壊します。
セミコロン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.