私はHaskellが初めてで、ファンクターと適用ファンクターについて読んでいます。わかりました。ファンクターとその使用方法は理解していますが、なぜapplicativeファンクターが便利で、Haskellでどのように使用できるかはわかりません。応用ファンクタが必要な理由を簡単な例で説明できますか?
Applicative functors は、ファンクターと monads の中間点を提供する構造であり、したがって、モナドよりも広く普及していますが、ファンクターよりも便利です。通常、ファンクターに関数をマッピングするだけです。適用可能なファンクターを使用すると、「通常の」関数(非関数引数を取る)を使用して、ファンクターコンテキストにあるいくつかの値を操作できます。結果として、これはモナドなしで効果的なプログラミングを提供します。
例に満ちた自己完結型の素敵な説明を見つけることができます こちら 。また、 実用的な構文解析の例 Bryan O'Sullivanが開発しました。事前知識は不要です。
適用可能なファンクターは、アクションの順序付けが必要な場合に便利ですが、中間結果に名前を付ける必要はありません。したがって、これらはモナドよりも弱いですが、ファンクターよりも強力です(明示的なバインド演算子はありませんが、ファンクター内で任意の関数を実行できます)。
それらはいつ有用ですか?一般的な例は解析です。解析では、データ構造の一部を順番に読み取る多数のアクションを実行し、すべての結果を結合します。これは、関数合成の一般的な形式に似ています。
f a b c d
a
、b
などを実行する任意のアクション、f
を結果に適用するファンクターと考えることができます。
f <$> a <*> b <*> c <*> d
私はそれらをオーバーロードされた「ホワイトスペース」と考えるのが好きです。または、その通常のHaskell関数は、アイデンティティ適用ファンクターにあります。
「 エフェクトを使用した適用プログラミング 」を参照してください
コナー・マクブライドとロス・パターソンの Functional Pearl スタイルにはいくつかの良い例があります。また、そもそもスタイルを普及させる責任もあります。彼らは「適用ファンクター」の「イディオム」という用語を使用しますが、それ以外はかなり理解できます。
あなたが必要な応用ファンクターを必要とする例を見出すのは難しいです。ほとんどの入門テキストは、便利なインターフェースとしてのみApplicative Functorを使用してMonadsから派生したインスタンスを提示しているので、中間のHaskellプログラマーがその質問をする理由を理解できます。
ここと主題のほとんどの紹介の両方で述べたように、重要な洞察は、Applicative FunctorはFunctorとMonadの間(FunctorとArrowの間でも)にあるということです。すべてのMonadはApplicative Functorですが、すべてのFunadsがApplicativeではありません。
必然的に、モナドのコンビネータを使用できないものに適用可能なコンビネータを使用できる場合があります。そのようなものの1つは ZipList
(詳細は this SO質問 も参照)です。これは単なるラッパーですリストのMonadインスタンスから派生したインスタンスとは異なる異なるApplicativeインスタンスを持つためにリストします。Applicativeドキュメントでは、次の行を使用して直感的な概念を示します。 ZipList
の目的:
f <$> ZipList xs1 <*> ... <*> ZipList xsn = ZipList (zipWithn f xs1 ... xsn)
指摘したように ここ 、ZipListでほとんど動作する風変わりなMonadインスタンスを作成することが可能です。
モナドではない他のApplicative Functorがあり( this SOの質問を参照)、それらは簡単に思いつきます。 、しかし、時々Monadを作成することは非効率的、複雑、または不可能ですらあり、それがあなたneedApplicative Functorを必要とするときです。
免責事項:Applicative Functorの作成は非効率、複雑、不可能な場合があります。疑問がある場合は、Applicative Functorの正しい使用法について地元のカテゴリ理論家に相談してください。
私の経験では、Applicativeファンクターは次の理由で優れています。
特定の種類のデータ構造は、強力なタイプの合成を受け入れますが、実際にモナドにすることはできません。実際、関数型リアクティブプログラミングの抽象化のほとんどは、このカテゴリに分類されます。技術的には、たとえばBehavior
(別名Signal
)モナド、通常は効率的に実行できません。 Applicative Functorを使用すると、効率を犠牲にすることなく強力なコンポジションを使用できます(確かに、モナドよりもApplicativeを使用する方が、作業する構造があまりないという理由で、少し注意が必要です)。
適用可能なファンクターにデータ依存性がないため、たとえばデータを利用可能にせずに、アクションが発生する可能性のあるすべての効果を探すアクションをトラバースしますそのため、次のように使用される「Webフォーム」アプリケーションを想像できます。
userData = User <$> field "Name" <*> field "Address"
そして、使用するすべてのフィールドを検索してフォームに表示するエンジンを作成し、データを取得したら、それを再度実行して、構築されたUser
を取得します。モナドでは次のように表現できるため、プレーンファンクタ(2つの形式を1つに結合するため)やモナドではできません。
userData = do
name <- field "Name"
address <- field $ name ++ "'s address"
return (User name address)
2番目のフィールドの名前は、最初のフィールドからの応答がないと認識できないためです。このフォームのアイデアを実装するライブラリがあると確信しています-私はこのプロジェクトとそのプロジェクトのために自分で何度かロールバックしました。
適用可能なファンクターのもう1つの利点は、composeです。より正確には、構成ファンクター:
newtype Compose f g x = Compose (f (g x))
f
とg
がいつでも適用されます。モナドについても同じことが言えません。これは、モナド変換のストーリー全体を作成しますが、これはいくつかの不快な点で複雑です。このように、アプリケーションは非常にクリーンであり、小さな構成可能なコンポーネントに焦点を当てることにより、必要なタイプの構造を構築できることを意味します。
最近、GHCにApplicativeDo
拡張機能が登場しました。これにより、アプリケーションでdo
表記を使用できるようになり、モナディなことをしない限り、表記の複雑さの一部が緩和されます。
1つの良い例:適用的解析。
[real world haskell] ch16を参照してください http://book.realworldhaskell.org/read/using-parsec.html#id652517
これは、do表記を使用したパーサーコードです。
-- file: ch16/FormApp.hs
p_hex :: CharParser () Char
p_hex = do
char '%'
a <- hexDigit
b <- hexDigit
let ((d, _):_) = readHex [a,b]
return . toEnum $ d
ファンクターを使用して作成する はるかに短い:
-- file: ch16/FormApp.hs
a_hex = hexify <$> (char '%' *> hexDigit) <*> hexDigit
where hexify a b = toEnum . fst . head . readHex $ [a,b]
「持ち上げる」ことは、いくつかの繰り返しコードの基本的な詳細を隠すことができます。少ない単語を使用して、正確で正確なストーリーを伝えることができます。
this をご覧になることもお勧めします
記事の最後に例があります
import Control.Applicative
hasCommentA blogComments =
BlogComment <$> lookup "title" blogComments
<*> lookup "user" blogComments
<*> lookup "comment" blogComments
これは、適用可能なプログラミングスタイルのいくつかの機能を示しています。