Haskellを探検しましょう! 応用ファンクター

直近では、 No Starch Pressは 、優れたLearn You a Haskell for Great Good!の印刷版を作成して発行してい ます。 (オンライン版)MiranLipovačaによって書かれました。



第11章Applicative functorsの最新の翻訳をお見せしたいと思います。その原作は、 No Starch Pressの出版物で、印刷に適合しています。



応用ファンクター



Haskellの清浄度、高次関数、パラメーター化された代数データ型、および型クラスの組み合わせにより、他の言語よりも多態性が単純になります。 大きな階層に属する型について考える必要はありません。 代わりに、型がどのように機能するかを調べ、適切な型クラスを使用して型をバインドします。 Int



は、エンティティのセットとして動作できます-比較されたエンティティ、順序付けられたエンティティ、列挙されたエンティティなど。



型クラスは開いています。つまり、独自のデータ型を定義し、その動作方法を考え、その動作を定義する型クラスに関連付けることができます。 新しい型クラスを導入し、既存の型をそのインスタンスにすることもできます。 このため、また型宣言によってのみ関数について多くのことを知ることができる優れたHaskell型システムのおかげで、非常に一般的で抽象的な動作を記述する型クラスを定義できます。



2つの要素が等しいかどうかを確認し、2つの要素を何らかの順序で比較して比較する操作を定義する型クラスについて説明しました。 これらは非常に抽象的でエレガントな動作ですが、私たちはほとんど特別な何かとは考えていませんが、私たちのほとんどの人生でそれらを扱っているからです。 第7章では、値を表示できる型であるファンクターが導入されました。 これは、型クラスが記述できる、有用でありながら依然として抽象的なプロパティの例です。 この章では、ファンクターを詳しく見ていきます。また、ファンクターの少し強力で便利なバージョンを使用します。これは、アプリカティブファンクターと呼ばれます。



ファンクターが戻る





第7章で学んだように、ファンクターは、リスト、 Maybe



値、ツリーなど、表示できるエンティティです。 Haskellでは、これらはFunctor



型クラスによって記述されます。このクラスには、 fmap



1つの型クラスメソッドのみが含まれます。 fmap



fmap :: (a -> b) -> fa -> fb



で、「aを取り、bと、a(または複数のa)を含むボックスを返す関数を指定してください。内部のb



(またはいくつかのb



)。「ボックス内の要素に関数を適用します。



ファンクタの値は、追加のコンテキストを持つ値として認識することもできます。 たとえば、値には、計算が失敗する可能性がある追加のコンテキストが含まれている場合があります。 リストに関連して、コンテキストは、値が複数の場合もあれば、存在しない場合もあります。 fmap



は、コンテキストを維持しながら関数を値に適用します。



型コンストラクタをFunctor



インスタンスにしたい場合、 * -> *



という形式でなければなりません。これは、型パラメーターとして具体的な型を1つだけとることを意味します。 たとえば、 Maybe



Maybe Int



Maybe String



など、特定のタイプの製品に対して1つのタイプパラメーターを受け取るため、 Maybe



をインスタンスにすることができます。 タイプコンストラクターがEither,



ような2つのパラメーターを取る場合、パラメーターが1つだけになるまで、タイプコンストラクターを部分的に適用する必要があります。 したがって、 Functor Either where



は記述できませんFunctor Either where



Functor (Either a) where



記述できます。 次に、 fmap



Either a



でのみ動作することを想像していた場合、次のタイプの説明があります。



 fmap :: (b -> c) -> Either ab -> Either ac
      
      







ご覧のとおり、 Either a



1つの型パラメーターのみを受け入れるため、 Either a



の部分Either a



固定されています。



ファンクターとしてのI / Oアクション





これまでに、 Functor



インスタンスである型(正確には、型コンストラクター)の数を調べてきました: []



およびMaybe



Either a



および第7章で作成したTree



型。善のために関数を使用する。 次に、 IO



インスタンスを見てみましょう。



たとえば、ある種の値がIO String



のタイプである場合、それは実際の世界に出て何らかの結果の文字列を取得するI / Oアクションであることを意味し、結果として返されます。 do構文で<-



を使用して、この結果を名前にバインドできます。 第8章では、I / Oアクションが外に出て外の世界から何らかの意味を持つ小さな足のある箱のように見える方法について説明しました。 それらがもたらしたものを見ることができますが、表示した後、値をIO



にラップする必要があります。 この脚付きボックスの類似性を見ると、 IO



がファンクターとしてどのように機能するかを理解できます。



IO



Functor



インスタンスである方法を見てみましょう。 関数を使用してI / Oアクションを表示するためにfmap



を使用するとき、同じことを行うI / Oアクションを取得したいのですが、関数は結果の値に適用されます。 コードは次のとおりです。

 instance Functor IO where fmap f action = do result <- action return (f result)
      
      





I / Oアクションに何かを表示した結果がI / Oアクションであるため、すぐにdo



構文を使用して2つのアクションを結合し、1つの新しいアクションを作成します。 fmap



の実装では、最初のI / Oアクションを最初に実行する新しいI / Oアクションを作成し、結果にresultという名前を付けます。 次に、 return (f result)



ます。 return



は、何もしないが結果として何かを返すだけのI / Oアクションを作成する関数であることを思い出してください。



do



ブロックが生成するアクションは、常に最後のアクションの結果値を返します。 これが、 return



を使用して、実際には何もせず、新しいI / Oアクションのf result



としてf result



を単に返すI / Oアクションを作成する理由です。 次のコードをご覧ください。



 main = do line <- getLine let line' = reverse line putStrLn $ "You said " ++ line' ++ " backwards!" putStrLn $ "Yes, you really said" ++ line' ++ " backwards!
      
      







ユーザーは文字列の入力を求められ、それをユーザーに返しますが、反転します。 fmap



を使用してこれを書き換える方法は次のとおりです。



 main = do line <- fmap reverse getLine putStrLn $ "You said " ++ line ++ " backwards!" putStrLn $ "Yes, you really said" ++ line ++ " backwards!"
      
      







エリリアン fmap reverse



Just "halb"



を表示してJust "halb"



取得できるgetLine



fmap reverse



getLine



を表示できます。 getLine



は、 IO String



型のI / Oアクションであり、 reverse



表示すると、現実世界に出て文字列を取得し、その結果にreverse



を適用するI / Oアクションが得られます。 Maybe



ボックスの内側に関数を適用できるのと同じ方法で、 IO



ボックスの内側に関数を適用できますが、何かを取得するには実世界に出る必要があります。 次に、 <-



を使用して結果を名前にバインドすると、名前には、すでにreverse



が適用された結果が反映されます。



fmap (++"!") getLine



I / O操作fmap (++"!") getLine



getLine



とまったく同じように動作しますが、結果に常に"!"



追加されます"!"



最後まで!



fmap



IO



のみで機能する場合、 fmap :: (a -> b) -> IO a -> IO b



ます。 fmap



は関数とI / O操作を受け入れ、古いI / O操作に似た新しいI / O操作を返しますが、それに含まれる結果に関数が適用されます。



関数を適用するためだけにI / O操作の結果を名前に関連付け、次の結果に別の名前を付けるという状況に陥った場合は、 fmap



使用を検討してください。 ファンクター内のいくつかのデータに複数の関数を適用する場合、最上位で関数を宣言するか、ラムダ関数を作成するか、理想的には関数の構成を使用できます。



 import Data.Char import Data.List main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine putStrLn line
      
      







このコードを実行してそこで「hello」と入力すると、次のようになります。

 $ runhaskell fmapping_io hello there EREHT- -OLLEH
      
      





関数がintersperse '-' . reverse . map toUpper



intersperse '-' . reverse . map toUpper



intersperse '-' . reverse . map toUpper



は文字列をtoUpper



toUpper



で表示し、この結果にreverse



を適用し、この結果にintersperse '-'



を適用します。 これは、次のコードを記述するより美しい方法です。



(\xs -> intersperse '-' (reverse (map toUpper xs)))







ファンクターとして機能





これまでずっと扱ってきた別のFunctor



インスタンスは(->) r



です。 待って! (->) r



どういう意味ですか? 関数r -> a



の型は、 (+) 2 3



形式で2 + 3



を書くことができるように、 (->) ra



形式で書き換えることができます。 (->) ra



として認識した場合(->)



わずかに異なる観点から(->)



ます。 これは、 Either



同様に、2つの型パラメーターをとる単純な型コンストラクターです。



ただし、型コンストラクターがFunctor



インスタンスにできるように、型コンストラクターは1つの型パラメーターのみを受け入れる必要があることに注意してください。 これがFunctor



インスタンスを作成できない(->)



理由です。 ただし、 (->) r



前に部分的に適用する場合、これは問題にはなりません。 構文でセクションを使用して型コンストラクターを部分的に適用できる場合(2+)



実行して(2+)



実行することで+



を適用する方法)、 (->) r



(r->)



として記述できます。



関数はどのように機能しますか? では、 Control.Monad.Instances



ある実装を見てみましょう。



 instance Functor ((->) r) where fmap fg = (\x -> f (gx))
      
      







最初にfmap



タイプについて考えてみましょう:

 fmap :: (a -> b) -> fa -> fb
      
      





次に、ファンクターインスタンスが果たす役割であるすべてのf



(->) r



精神的に置き換えましょう。 これにより、この特定のインスタンスの場合にfmap



がどのように動作するかを理解できます。 結果は次のとおりです。

 fmap :: (a -> b) -> ((->) ra) -> ((->) rb)
      
      





ここで、タイプ(->) ra



および(->) rb



を中置形式で記述できます。

r -> a



およびr -> b



、通常関数で行うように:

 fmap :: (a -> b) -> (r -> a) -> (r -> b)
      
      





いいね ある関数を別の関数にマッピングすると、 Maybe



を関数にマッピングするとMaybe



が生成され、関数を含むリストを表示するとリストが生成されるように、関数が生成されます。 前のタイプは何を教えてくれますか? a



からb



への関数とr



からa



への関数を取り、 r



からb



への関数を返すことがわかります。 これは何かを思い出させますか? はい、機能の構成! 出力r -> a



を入力a- a -> b



して、関数r -> b



a -> b



を取得します。これは、まさに関数の構成です。 このインスタンスを記述する別の方法を次に示します。

 instance Functor ((->) r) where fmap = (.)
      
      





このコードにより、関数へのfmap



適用は単なる関数の合成であることが明確になります。 スクリプトでControl.Monad.Instances



インポートします。これは、このインスタンスが定義されているモジュールであるため、スクリプトをロードして、関数の表示で遊んでみてください。

 ghci> :t fmap (*3) (+100) fmap (*3) (+100) :: (Num a) => a -> a ghci> fmap (*3) (+100) 1 303 ghci> (*3) `fmap` (+100) $ 1 303 ghci> (*3) . (+100) $ 1 303 ghci> fmap (show . (*3)) (*100) 1 "300"
      
      





fmap



を中置関数として呼び出して、に似たものにすることができます.



明示的でした。 入力の2行目に、 (*3)



を使用して(+100)



を表示します。これは、入力を受け入れ、それに適用(+100)



、この結果に適用(*3)



する関数を提供します。 次に、この関数を1



適用します。



すべてのファンクターと同様に、関数はコンテキストを持つ値として認識できます。 (+3)



ような関数がある場合、値を関数の最終結果とみなすことができ、結果を得るためにこの関数を何かに適用する必要があるというコンテキストがあります。 fmap (*3)



(+100)



適用すると、 (+100)



と同じ働きをする別の関数が作成されますが、結果を返す前に、この結果に(*3)



が適用されます。



fmap



が関数に適用されたときに関数の構成であるという事実は、現時点ではあまり有用ではありませんが、少なくとも非常に興味深いものです。 また、心が少し変わり、ボックス( IO



および(->) r



)よりも計算のように振る舞うエンティティがファンクターになる方法を確認できます。 関数を使用して計算を表示すると、同じタイプの計算が返されますが、この計算の結果は関数によって変更されます。



リフター

fmap



が従うべき法則に移る前に、 fmap



タイプについてもう一度考えてみましょう。



 fmap :: (a -> b) -> fa -> fb
      
      







第5章のカリー化された関数の概要は、Haskellのすべての関数が実際に1つのパラメーターを取るというステートメントから始まりました。 関数a -> b -> c



実際にはタイプa



パラメーターを1つだけ受け取り、その後関数b -> c



を返しc



。関数b -> c



、1つのパラメーターを受け取りc



を返しc



。 そのため、パラメータが不十分な関数(その部分的なアプリケーション)を呼び出すと、スキップしたいくつかのパラメータを受け入れる関数が返されます(複数のパラメータを受け入れるかのように関数を再度認識する場合)。 したがって、a- a -> (b -> c)



と書くとa -> (b -> c)



カリー化がより明確になります。



同様に、 fmap :: (a -> b) -> (fa -> fb)



を書くと、 fmap :: (a -> b) -> (fa -> fb)



は1つの関数とファンクター値を取り、ファンクター値を返す関数としてではなく、関数として認識することができます。関数を受け取り、前の関数と同じ新しい関数を返します。ただし、ファンクター値をパラメーターとして受け取り、結果としてファンクター値を返します。 関数fa -> fb



a -> b



を取り、関数fa -> fb



を返します。 これは「リフティング機能」と呼ばれます。 GHCiの: :t



コマンドを使用して、このアイデアを試してみましょう。



 ghci> :t fmap (*2) fmap (*2) :: (Num a, Functor f) => fa -> fa ghci> :t fmap (replicate 3) fmap (replicate 3) :: (Functor f) => fa -> f [a]
      
      







fmap (*2)



は、数値に対してファンクターf



を取り、数値に対してファンクターを返す関数です。 このファンクターは、リスト、 Maybe



Either String



、またはその他のEither String



かです。 式fmap (replicate 3)



は、任意のタイプのファンクターを受け取り、このタイプの要素のリストのファンクターを返します。 これは、たとえばfmap (++"!")



を部分的に適用し、GHCiの名前にバインドするとさらに明白になります。



次の2つの方法でfmap



認識できます。







両方の視点が正しい。



タイプfmap (replicate 3) :: (Functor f) => fa -> f [a]



ファンクターfmap (replicate 3) :: (Functor f) => fa -> f [a]



は、関数が任意のファンクターで機能することを意味します。 彼女が何をするかは、ファンクター次第です。 リストにfmap (replicate 3)



を適用すると、リストのfmap



実装が選択されmap



。つまり、ただmap



です。 Maybe a



に適用replicate 3



と、 Just



内の値にreplicate 3



が適用replicate 3



れます。 この値がNothing



場合、 Nothing



に等しくなります。 以下に例を示します。



 ghci> fmap (replicate 3) [1,2,3,4] [[1,1,1],[2,2,2],[3,3,3],[4,4,4]] ghci> fmap (replicate 3) (Just 4) Just [4,4,4] ghci> fmap (replicate 3) (Right "blah") Right ["blah","blah","blah"] ghci> fmap (replicate 3) Nothing Nothing ghci> fmap (replicate 3) (Left "foo") Left "foo"
      
      







ファンクターの法則





すべてのファンクターが特定のタイプのプロパティと動作を示すと想定されます。 表示可能なエンティティとして確実に動作する必要があります。 ファンクターにfmap



を適用すると、関数を持つファンクターのみが表示され、それ以上は表示されません。 この動作は、ファンクターの法則で説明されています。 Functor



すべてのインスタンスは、これら2つの法律に従う必要があります。 Haskellはこれらの法則を自動的に実行することを強制しないので、ファンクターを作成するときに自分でそれらをチェックする必要があります。 標準ライブラリのすべてのFunctor



インスタンスはこれらの法律に従います。



第1幕





ファンクタの最初の法則は、ファンクタの値にid



関数を適用する場合、取得するファンクタの値はファンクタの元の値と同じでなければならないことを示しています。 もう少し正式には、これはfmap id = id



意味します。 基本的に、これは、 fmap id



をファンクターの値に適用する場合、単にid



を値に適用するのと同じであるべきだと言います。 id



は、パラメータを変更せずに単に返すアイデンティティ関数であることを思い出してください。 \x -> x



と書くこともできます。 ファンクタの値を表示可能なものとしてとると、 fmap id = id



法則は非常に簡単で明白に見えます。



この法則がファンクターのいくつかの値に当てはまるかどうか見てみましょう。



 ghci> fmap id (Just 3) Just 3 ghci> id (Just 3) Just 3 ghci> fmap id [1..5] [1,2,3,4,5] ghci> id [1..5] [1,2,3,4,5] ghci> fmap id [] [] ghci> fmap id Nothing Nothing
      
      







たとえばMaybe



fmap



の実装を見ると、ファンクターの最初の法則が成り立つ理由を理解できます。



 instance Functor Maybe where fmap f (Just x) = Just (fx) fmap f Nothing = Nothing
      
      







この実装では、 id



がパラメーターf



役割を果たすと考えられます。 fmap id



Just x



適用すると、結果はJust (id x)



になり、 id



単にパラメーターを返すだけなので、 Just (id x)



Just x



と等しいと結論付けることができます。 したがって、 Just



値コンストラクターを使用して作成されたMaybe



値にid



を適用すると、同じ値が返されることがわかりました。



id



Nothing



適用すると同じ値が返されることは簡単です。 したがって、 fmap



の実装におけるこれら2つの等式から、法則fmap id = id



が尊重されることがわかります。



第2幕





正義 2番目の法則では、2つの関数を合成し、結果の関数をファンクターに適用すると、最初の関数をファンクターに適用してから別の関数を適用するのと同じ結果が得られます。 正式な表記では、これはfmap (f . g) = fmap f . fmap g



意味しfmap (f . g) = fmap f . fmap g



fmap (f . g) = fmap f . fmap g



。 または、別の方法で記述した場合、ファンクターx



任意の値x



次が成り立つはずです: fmap (f . g) x = fmap f (fmap gx)







特定のタイプがファンクターの2つの法則に従うことを特定できれば、マッピングに関しては他のファンクターと同じ基本的な動作を期待できます。 fmap



を適用すると、表示以外のカーテンの背後では何も起こらず、表示可能なエンティティ、つまりファンクターとして機能することがわかります。



特定の型の2番目の法則がどのように成り立つかを調べるには、その型のfmap



実装を見て、最初の法則に従っているかどうMaybe



を確認するために使用した方法を使用します。 したがって、ファンクターの2番目の法則がMaybe



にどのように当てはまるかを確認するために、 fmap (f . g)



Nothing



に適用すると、 Nothing



が得られます。 fmap f (fmap g Nothing)



を実行すると、同じ理由でNothing



を取得します。



Maybe



値が等しい場合に2番目の法則がどのように成り立つかを見るのは非常に簡単Nothing



です。しかし、その値がJust



どうなるのでしょうか?私たちがしなければまあ、fmap (f . g) (Just x)



、実現から我々はそれは次のように実装されていることがわかりますJust ((f . g) x)



同様のものをJust (f (gx))



。我々が行う場合にはfmap f (fmap g (Just x))



、私たちには見え実装から、それがfmap g (Just x)



ありますJust (gx)



。したがって、それfmap f (fmap g (Just x))



は等しいfmap f (Just (gx))



ですが、実装からは等しいことがわかりJust (f (gx))



ます。



この証明に少し混乱していても心配しないでください。関数の構成がどのように機能するかを理解してください。型はコンテナまたは関数のように機能するため、これらの法律がどのように適用されるかを直感的に理解できることがよくあります。また、タイプのいくつかの異なる値でそれらをチェックし、タイプが実際にこれらの法則に従っていることをある程度の確実性で言うことができます。



法律違反





型コンストラクターの病理学的例を見てみましょう。これはFunctor型クラスのインスタンスですが、法則に準拠していないため、ファンクターではありません。次のタイプがあるとします。

 data CMaybe a = CNothing | CJust Int a deriving (Show)
      
      





C



ここはカウンターの略です。これは、たぶんaによく似たデータ型で、一部のみJust



が1つではなく2つのフィールドを含んでいます。値コンストラクターの最初のフィールドCJust



は常にtype Int



を持ち、一種のカウンターになります。また、2番目のフィールドにはa



typeパラメーターから取得したタイプがあり、そのタイプは選択した特定のタイプに依存しますCMaybe a



新しいタイプで遊んでみましょう:



 ghci> CNothing CNothing ghci> CJust 0 "haha" CJust 0 "haha" ghci> :t CNothing CNothing :: CMaybe a ghci> :t CJust 0 "haha" CJust 0 "haha" :: CMaybe [Char] ghci> CJust 100 [1,2,3] CJust 100 [1,2,3]
      
      







コンストラクターを使用する場合CNothing



、フィールドはありません。constructorを使用する場合CJust



、最初のフィールドは整数であり、2番目のフィールドは任意のタイプです。この型をインスタンスにしてFunctor



、使用するたびfmap



に関数が2番目のフィールドに適用され、最初のフィールドがだけ増加するようにし1



ます。



 instance Functor CMaybe where fmap f CNothing = CNothing fmap f (CJust counter x) = CJust (counter+1) (fx)
      
      







これは、のインスタンス実装に部分的に似ていますが、空のボックス(値)を表さない値Maybe



に適用fmap



する場合にのみCJust



、コンテンツに関数を適用するだけでなく、カウンタを増やし1



ます。これまでのところ、すべてがクールなようです。これで少し遊ぶこともできます:



 ghci> fmap (++"ha") (CJust 0 "ho") CJust 1 "hoha" ghci> fmap (++"he") (fmap (++"ha") (CJust 0 "ho")) CJust 2 "hohahe" ghci> fmap (++"blah") CNothing CNothing
      
      







このタイプはファンクターの法則に従いますか?何かが法律に従っていないことを確認するには、反例を1つだけ見つけるだけで十分です。



 ghci> fmap id (CJust 0 "haha") CJust 1 "haha" ghci> id (CJust 0 "haha") CJust 0 "haha"
      
      







ファンクターの最初の法則が示すように、idを使用してファンクターの値を表示する場合、ファンクターid



の同じ値で呼び出すだけと同じになります。この例は、これがファンクターに適用されないことを示していますCMaybe



。型classの一部ですが、Functor



ファンクターの所定の法則に従っていないため、ファンクターではありません。



これCMaybe



はファンクターではないので、1つのふりをしますが、ファンクターとして使用すると、コードの誤りにつながる可能性があります。ファンクタを使用する場合、最初にいくつかの関数を構成してからファンクタの値を表示するか、各関数でファンクタの値を単純に表示するかは関係ありません。しかし、使用する場合CMaybe



表示された回数を追跡するため、重要です。クールじゃない!私たちがしたい場合はCMaybe



ファンクタ法則に従う、我々が使用している場合、フィールドのIntが変更されていないことを確認する必要がありますfmap







最初は、ファンクターの法則は少し混乱して不必要に見えるかもしれません。しかし、あるタイプが両方の法律に従うことを知っている場合、それがどのように機能するかについて一定の仮定を立てることができます。型がファンクターの法則に従う場合、fmap



この型の値を持つ呼び出しは関数にのみ適用されることを知っています-それ以上は何もしません。これにより、法則を使用してファンクターの動作を判断したり、ファンクターで確実に機能する関数を作成したりできるため、より抽象的で拡張性の高いコードになります。



次回型をインスタンスにしたFunctor



ときは、しばらくしてファンクターの法則を満たしていることを確認してください。実装を1行ずつ実行して、法が守られているかどうかを確認したり、反例を見つけたりすることができます。十分な数のファンクターを検討した後、それらの共通のプロパティと動作を学習し、このタイプまたはそのタイプがファンクターの法則に従うかどうかを直感的に理解します。



Applicative Functorの使用



プレゼント

このセクションでは、拡張ファンクターである適用ファンクターを検討します。



これまで、1つのパラメーターのみをとる関数を使用してファンクターをマッピングすることに焦点を当ててきました。しかし、2つのパラメーターを取る関数を使用してファンクターを表示するとどうなりますか?いくつかの特定の例を見てみましょう:



持っJust 3



ていてfmap (*) (Just 3)



やっている場合、何が得られますか?インスタンスの実装Maybe



のためにFunctor



、我々は、この値があればということを知ってJust



、関数は内部の値に適用されますJust



。したがって、セクションを使用する場合、実行fmap (*) (Just 3)



はを返しJust ((*) 3)



、フォームに書き込むこともできJust (3 *)



ます。おもしろい!関数がラップされJust



ます!



ファンクター値内のいくつかの関数を次に示します。



 ghci> :t fmap (++) (Just "hey") fmap (++) (Just "hey") :: Maybe ([Char] -> [Char]) ghci> :t fmap compare (Just 'a') fmap compare (Just 'a') :: Maybe (Char -> Ordering) ghci> :t fmap compare "A LIST OF CHARS" fmap compare "A LIST OF CHARS" :: [Char -> Ordering] ghci> :t fmap (\xyz -> x + y / z) [3,4,5,6] fmap (\xyz -> x + y / z) [3,4,5,6] :: (Fractional a) => [a -> a -> a]
      
      







compare



type あるヘルプを使用して文字のリストを表示すると、関数はリスト内の文字を使用して部分的に適用されるため、(Ord a) => a -> a -> Ordering



typeの関数のリストを取得します。これはタイプ関数のリストではありません、最初に適用されたタイプがあったので、2番目がタイプを持つかどうか決定しなければならないのでChar -> Ordering



compare



(Ord a) => a -> Ordering



a



Char



a



Char







「マルチパラメーター」関数の助けを借りてファンクターの値を表示し、それ自体に関数を含むファンクターの値を取得する方法を確認します。それで、今それらで何ができるでしょうか?たとえば、これらの関数をパラメーターとして使用する関数を使用してそれらを表示できます。ファンクターの値に何があっても、パラメーターとして表示する関数に渡されるためです。



 ghci> let a = fmap (*) [1,2,3,4] ghci> :ta a :: [Integer -> Integer] ghci> fmap (\f -> f 9) a [9,18,27,36]
      
      







しかし、ファンクター値Just (3 *)



とファンクター値Just 5



があり、関数を抽出しJust (3 *)



て表示したい場合はJust 5



どうでしょうか?通常のファンクターでは、通常の関数を使用して既存のファンクターの表示のみをサポートするため、これは機能しません。関数を含むファンクターをで表示した場合でも、\f -> f 9



単純に通常の関数で表示しました。しかし、私たちfmap



提供するものを使用して、ファンクターの値の中にある関数の助けを借りて、ファンクターの別の値を表示することはできません。Just



サンプルでコンストラクターを一致させて、そこから関数を抽出し、それを使用して表示できますJust 5



が、ファンクターで機能するより一般的で抽象的なアプローチを探しています。



応用ファンクターに挨拶する





モジュールにあるApplicative型クラスに会いますControl.Applicative



それは2つの関数を定義していますpure



<*>



これらの関数のデフォルト実装は提供されないため、何かを適用可能なファンクターにしたい場合は、両方を定義する必要があります。このクラスは次のように定義されます:



 class (Functor f) => Applicative f where pure :: a -> fa (<*>) :: f (a -> b) -> fa -> fb
      
      







この単純な3行のクラス定義は、多くのことを教えてくれます!最初の行はクラス定義Applicative



始まり、クラス制約も導入します。型コンストラクターを型クラスの一部にしたい場合Applicative



、まず最初に型クラスに属しなければならないという制限がありFunctor



ます。そのため、型コンストラクタが型クラスに属していることがわかっている場合、型クラスApplicative



にも属しているFunctor



ため、それに適用できますfmap







彼が定義する最初のメソッドはと呼ばれpure



ます。彼のタイプ広告はのように見えpure :: a -> fa



ます。f



Applicative Functorのインスタンスの役割を果たします。 Haskellには非常に優れた型システムがあり、関数でできることはパラメーターを取得して値を返すだけなので、型宣言について多くのことを言うことができ、この型も例外ではありません。



pure



任意の型の値を取り、その値を内部に含む適用可能な値を返す必要があります。 「内部」という語句は、ボックスとの類似性を示していますが、常にテストに合格するとは限りません。しかし、タイプa -> fa



はまだかなり明白です。値を取得し、結果としてこの値を含む適用可能な値にラップします。想像するより良い方法pure



-値を取り、特定のデフォルトコンテキスト(または純粋なコンテキスト)に配置する-つまり、この値を返す最小コンテキスト。



この機能は<*>



本当に興味深いです。彼女はこのタイプ定義を持っています:



 f (a -> b) -> fa -> fb
      
      







何か思い出させますか?のように見えますfmap :: (a -> b) -> fa -> fb



この機能<*>



は一種の拡張機能として理解できfmap



ます。一方fmap



ファンクタの機能と意味を受け取り、値のファンクタ内に関数を適用する<*>



機能と別のファンクタを含み、それを用いて第二ファンクタを表示し、最初のファンクタから特徴を抽出する値のファンクタをとります。



応用ファンクター





のインスタンス実装Applicative



見てみましょうMaybe







 instance Applicative Maybe where pure = Just Nothing <*> _ = Nothing (Just f) <*> something = fmap f something
      
      







繰り返しますが、クラスの定義から、f



アプリケーションファンクタの役割を果たすが、パラメータとして1つの特定の型をとる必要があるため、Applicative Maybe where



代わりにinstanceを記述することがわかりますinstance Applicative (Maybe a) where







さらに、我々は持っていますpure



。関数は何かを受け入れ、適用可能な値にラップする必要があることを思い出してください。pure = Just



値コンストラクタはJust



通常の関数のように見えるため、書きまし。書くこともできますpure x = Just x







最後に、定義があります<*>



Nothing



関数は内部にないため、から関数を抽出できません。したがって、から関数を抽出しようとするNothing



と、結果はになりますNothing



クラス



定義Applicative



にはクラス制限がありますFunctor



これは、関数の両方のパラメーター<*>



がファンクター値であると想定できることを意味します。最初の引数がでなくNothing



Just



内部に何らかの関数がある場合、この関数では2番目のパラメーターを表示すると言います。このコードはNothing



Nothing



を使用する関数を使用したマッピングfmap



がを返すため、2番目の引数が存在する場合にも対応しますNothing



。したがって、Maybe



関数の場合<*>



、左側の値から関数を抽出し、それがJust



である場合、右側の値を表示します。パラメータのいずれかがの場合Nothing



、結果はになりますNothing







それでは試してみましょう:



 ghci> Just (+3) <*> Just 9 Just 12 ghci> pure (+3) <*> Just 10 Just 13 ghci> pure (+3) <*> Just 9 Just 12 ghci> Just (++"hahah") <*> Nothing Nothing ghci> Nothing <*> Just "woot" Nothing
      
      







あなたはの実装ことがわかりますpure (+3)



し、Just (+3)



この場合には、 -それは同じことです。使用pure



あなたが値を扱っている場合Maybe



(それらを使用して応用的文脈で<*>



)。そうでなければ、使用に固執しますJust







入力の最初の4行は、関数がどのように抽出され、表示に使用されるかを示していますが、この場合、これは単純にラップされていない関数をファンクターに適用することで実現できます。最後の行は、から関数を抽出し、Nothing



その助けを借りて何かを表示しようとするという点で興味深いNothing



です。



通常のファンクターを使用しているときに関数を使用してファンクターを表示すると、結果が部分的に適用された関数であっても、一般的な方法で結果を抽出できません。一方、適用可能なファンクターを使用すると、単一の関数を使用して複数のファンクターを操作できます。



適用スタイル





型クラスを使用する場合Applicative



、関数の使用を<*>



チェーンに入れることができます。これにより、1つだけでなく複数の適用可能な値を一度に簡単に操作できます。たとえば、これを見てください:



 ghci> pure (+) <*> Just 3 <*> Just 5 Just 8 ghci> pure (+) <*> Just 3 <*> Nothing Nothing ghci> pure (+) <*> Nothing <*> Just 5 Nothing
      
      







クジラ関数+



を適用可能な値でラップし、それを使用<*>



して2つのパラメーターを使用して呼び出しました。どちらのパラメーターも適用可能な値です。



これがどのように起こるのか、一歩一歩見てみましょう。<*>



左結合、つまり:

 pure (+) <*> Just 3 <*> Just 5
      
      





これと同じ:

 (pure (+) <*> Just 3) <*> Just 5
      
      





最初に、関数+



は適用可能な値(この場合Maybe



は関数を含む値)に配置されます。そのため、pure (+)



基本的には同等Just (+)



です。次に、呼び出しが発生しますJust (+) <*> Just 3



。その結果はJust (3+)



です。これは、部分的なアプリケーションによるものです。3



関数にのみ適用すると、+



1つのパラメーターを取り、それに追加する関数が返されます3



。最後に、を実行Just (3+) <*> Just 5



し、resultとして戻りますJust 8







それは素晴らしいことではありませんか?!適用可能なファンクターと適用可能な計算スタイルpure f <*> x <*> y <*> ...



適用可能な値ではないパラメーターを期待する関数を使用し、この関数を使用して複数の適用可能な値を操作できます。関数は、オカレンス間で常に部分的に適用されるため、必要な数のパラメーターを取ることができます<*>



等しい



という事実を考慮すると、これはさらに便利で明白になります。これは適用法の1つです。この章の後半で適用法について詳しく説明しますが、ここでどのように適用されるか考えてみましょう。pure f <*> x



fmap fx



pure



値をデフォルトのコンテキストに入れます。関数を単にデフォルトのコンテキストに配置してから抽出し、別のアプリカティブファンクタ内の値に適用する場合、この関数でこのアプリカティブファンクタを単に表示するのと同じになります。書く代わりに、書くpure f <*> x <*> y <*> ...



ことができますfmap fx <*> y <*> ...



。これが、Control.Applicative



という関数をエクスポートする理由です。これは、中置演算子の形式の<$>



関数fmap



です。定義方法は次のとおりです。

 (<$>) :: (Functor f) => (a -> b) -> fa -> fb f <$> x = fmap fx
      
      







型変数は、パラメーター名や他の値の名前とは無関係であることを思い出してください。ここf



で、関数宣言にはクラス制約を持つ型変数があります。これは、置換する型コンストラクターf



は型classの一部である必要があることを示していFunctor



ます。f



関数の本体のは、表示に使用する関数を示しますx



f



両方のことを表すために使用したという事実は、それらが同じものを表すことを意味するものではありません。




使用する場合、<$>



applicativeスタイルはその栄光のすべてに現れますf



。3つのapplicative値に関数適用したい場合、次のように書くことができるからです。f <$> x <*> y <*> z



パラメータが通常の値である場合、と記述しfxyz



ます。



これがどのように機能するかを詳しく見てみましょう。我々は値を接続すると仮定Just "johntra"



し、Just "volta"



ファンクタ内にある1行でMaybe



これを行うことができます:

 ghci> (++) <$> Just "johntra" <*> Just "volta" Just "johntravolta"
      
      





これがどのように起こるかを見る前に、前の行とこれを比較してください:

 ghci> (++) "johntra" "volta" "johntravolta"
      
      





Applicative Functorで通常の関数を使用するには、いくつかの<$>



とを散らばっ<*>



てください。関数はApplicative値を処理し、Applicative値を返します。素晴らしいですね



私たちに戻る(++) <$> Just "johntra" <*> Just "volta"



:最初に(++)



(++) :: [a] -> [a] -> [a]



ディスプレイのタイプがありますJust "johntra"



これによりJust ("johntra"++)



、typeと同じ値を持ちMaybe ([Char] -> [Char])



ます。関数の最初のパラメーターが(++)



どのように食べられ、どのようa



に値変わったかに注目してくださいChar



そして今満足発現Just ("johntra"++) <*> Just "volta"



の機能を抽出Just



し、それを用いて表しJust "volta"



、その結果、Just "johntravolta"



2つの値のいずれかがの場合Nothing



、結果も次のようになります。Nothing







リスト



リスト(実際にはリスト型コンストラクター[]



)は、適用可能なファンクターです。なんて驚き!そして、ここに[]



インスタンスがありますApplicative





 instance Applicative [] where pure x = [x] fs <*> xs = [fx | f <- fs, x <- xs]
      
      





pure



値を取り、それをデフォルトのコンテキストに入れることを思い出してください。つまり、この値を返す最小限のコンテキストに配置します。リストの最小コンテキストは空のリストですが、空のリストは値がないことを意味するため、適用した値を含めることはできませんpure



。そのためpure



、値を取得してシングルトンリストに追加します。同様に、応用的ファンクタの最小コンテキストMaybe



であろうNothing



が、それは代わりに、値の値が存在しないことを意味し、これpure



のインスタンスの実装におけるMaybe



として実現しますJust







ここでpure



のアクションで:

 ghci> pure "Hey" :: [String] ["Hey"] ghci> pure "Hey" :: Maybe String Just "Hey"
      
      





<*>



?関数のタイプが<*>



リストのみに制限されている場合、を取得し(<*>) :: [a -> b] -> [a] -> [b]



ます。この関数は、リストジェネレーターによって実装されます。<*>



何らかの方法でその左側のパラメーターから関数を抽出し、そのヘルプを使用して右側のパラメーターを表示する必要があります。ただし、左のリストには関数が含まれていないか、1つの関数または複数の関数が含まれている場合があり、右のリストには複数の値が含まれている場合があります。そのため、リストジェネレーターを使用して両方のリストから抽出します。左のリストから可能なすべての関数を、右のリストから可能なすべての値に適用します。結果のリストには、左のリストの関数を右の値に適用するすべての可能な組み合わせが含まれています。



次の<*>



ようなリストで使用できます

 ghci> [(*0),(+100),(^2)] <*> [1,2,3] [0,0,0,101,102,103,1,4,9]
      
      





左のリストには3つの関数が含まれ、右のリストには3つの値が含まれるため、結果のリストには9つの要素が含まれます。左のリストの各関数は、右の各要素に適用されます。2つのパラメーターを取る関数のリストがある場合、2つのリストの間にこれらの関数を適用できます。



次の例では、2つのリストの間に2つの関数を適用します。

 ghci> [(+),(*)] <*> [1,2] <*> [3,4] [4,5,5,6,3,4,6,8]
      
      





<*>



左側の各関数は右側の各値に適用されるため、左結合であるため、最初に実行され[(+),(*)] <*> [1,2]



、と同じリスト[(1+),(2+),(1*),(2*)]



が作成されます。その後[(1+),(2+),(1*),(2*)] <*> [3,4]



、最終結果を返す実行されます。



リストでapplicativeスタイルを使用するのは素晴らしいことです!

 ghci> (++) <$> ["ha","heh","hmm"] <*> ["?","!","."] ["ha?","ha!","ha.","heh?","heh!","heh.","hmm?","hmm!","hmm."]
      
      





この場合も、対応する適用演算子を挿入するだけで、2つの行のリストの間に2行を取る通常の関数を使用しました。



リストは非決定的な計算と考えることができます。100



またはなどの値"what"



は、結果が1つしか[1,2,3]



ない決定論的計算と見なすことができますが、リストのような値は、どの結果が必要かを判断できない計算と見なすことができるため、すべての可能な結果が返されます。したがって、このようなものを記述する場合、結果がさらに不確実な別の非決定的計算を作成することを(+) <$> [1,2,3] <*> [4,5,6]



唯一の+



目的とする、2つの非決定的計算の組み合わせと考えることができます



多くの場合、リストでアプリケーションスタイルを使用すると、リストジェネレーターの代わりになります。第1章では、作品[2,5,10]



との可能な組み合わせをすべて見たかった[8,10,11]



ので、次のようにしました。

 ghci> [ x*y | x <- [2,5,10], y <- [8,10,11]] [16,20,22,40,50,55,80,100,110]
      
      





両方のリストから値を抽出し、要素の各組み合わせの間に関数を適用するだけです。これは、適用スタイルでも実行できます。



 ghci> (*) <$> [2,5,10] <*> [8,10,11] [16,20,22,40,50,55,80,100,110]
      
      







私にとって、このアプローチはより理解しやすいものです。なぜなら*



、2つの非決定的計算の間で単に呼んでいるものを理解するのが簡単だからです。これら2つのリストの要素であるlargeのすべての可能な積を取得し50



たい場合、以下を使用します。



 ghci> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11] [55,80,100,110]
      
      







pure f <*> xs



リストを使用するときの呼び出しが同等であることは簡単にわかりますfmap f xs



pure f



-シンプル[f]



ですが[f] <*> xs



、左のリストの各関数を右の各値に適用しますが、左のリストには関数が1つしかないため、表示のように見えます。



IOは適用可能なファンクターでもあります





Applicative



私たちがすでに会ったもう1つの例IO



です。このインスタンスの実装方法は次のとおりです。

 instance Applicative IO where pure = return a <*> b = do f <- a x <- b return (fx)
      
      





騎士

本質pure



は、結果として値を含む最小限のコンテキストに値を配置することであるため、pure



単純であることが論理的ですreturn



return



何もしないI / Oアクションを作成します。端末への印刷やファイルからの読み取りなどの入出力操作を実行せずに、結果として何らかの値を返すだけです。



私が<*>



働くことに限られていればIO



、彼女はタイプを持つでしょう(<*>) :: IO (a -> b) -> IO a -> IO b



。の場合、関数を返すIO



I / Oアクションa



取り、I / Oアクションを実行し、この関数をに関連付けますf



。次に、I / Oアクションb



実行し、その結果をに関連付けx



ます。最後に、彼女は関数を適用しますf



k x



および結果としてこのアプリケーションの結果を返します。これを実装するために、ここで構文を使用しましたdo



。 (構文の本質は、do



複数のI / Oアクションを取り、それらを1つのアクションに結合することです。)and



を使用する場合、関数のアプリケーションは、左側のパラメーターから関数を抽出し、それを右側に適用するものとして認識できますパラメータ。についてMaybe



[]



<*>



IO



抽出は有効なままですが、2つのI / Oアクションを実行してそれらを1つに結合するため、抽出を順番に行うという概念があります。最初のI / Oアクションから関数を抽出する必要がありますが、I / Oアクションから結果を抽出するには、後者を実行する必要があります。これを考えてください:



 myAction :: IO String myAction = do a <- getLine b <- getLine return $ a ++ b
      
      







これは、ユーザーに2行を要求し、結果としてこれら2行の連結を返すI / Oアクションです。2つのI / OアクションgetLine



結合することでこれを達成しました。return



新しい接着されたI / Oアクションに実行結果を含める必要があるためですa ++ b



これを綴るもう1つの方法は、applicativeスタイルを使用することです。



 myAction :: IO String myAction = (++) <$> getLine <*> getLine
      
      







これは、他の2つのI / Oアクションの結果の間に関数を適用するI / Oアクションを作成したときと同じことです。getLine



これは、タイプを持つI / Oアクションであることを思い出してくださいgetLine :: IO String



<*>



2つの適用可能な値の間で適用する場合、結果は適用可能な値であるため、すべてが理にかなっています。



ボックスの類推に戻ると、それgetLine



は現実の世界に入り込み文字列をもたらすボックスとして想像できます。実行(++) <$> getLine <*> getLine



すると、別の大きなボックスが作成され、2つのボックスが端末から文字列を受信するために送信され、結果としてこれら2つの文字列の連結が返されます。



(++) <$> getLine <*> getLine



はタイプですIO String



これは、この式が他の入出力アクションと同様に完全に通常の入出力アクションであり、結果の値も返すことを意味します。これが、そのようなことができる理由です。



 main = do a <- (++) <$> getLine <*> getLine putStrLn $ "The two lines concatenated turn out to be: " ++ a
      
      







Applicative Functorとして機能





別のインスタンスApplicative



(->) r



またはです。アプリケーションスタイルで関数を使用することはあまりありませんが、それでも概念は非常に興味深いので、関数インスタンスの実装方法を見てみましょう。



 instance Applicative ((->) r) where pure x = (\_ -> x) f <*> g = \x -> fx (gx)
      
      







値をapplicative値でラップするとpure



、返される結果はその値になります。最小のデフォルトコンテキストは、結果としてこの値を返します。そのため、関数インスタンスの実装でpure



は、値を受け取り、渡されたパラメーターを無視して常にその値を返す関数を作成します。pure



インスタンスのタイプ(->) r



次のようになりpure :: a -> (r -> a)



ます。



 ghci> (pure 3) "blah" 3
      
      







カリー化のため、関数の適用は左結合なので、括弧を省略できます。



 ghci> pure 3 "blah" 3
      
      







インスタンスの実装は<*>



少しわかりにくいので、関数をアプリカティブスタイルのアプリカティブファンクタとして使用する方法を見てみましょう。



 ghci> :t (+) <$> (+3) <*> (*100) (+) <$> (+3) <*> (*100) :: (Num a) => a -> a ghci> (+) <$> (+3) <*> (*100) $ 5 508
      
      







<*>



2つのapplicative値を持つ関数呼び出しはapplicative値を返すため、2つの関数で呼び出すと関数が得られます。ここで何が起こっていますか?我々が行うとき(+) <$> (+3) <*> (*100)



、私たちは適用される関数の作成+



関数の結果に(+3)



して(*100)



、その値を返します。呼び出されたとき(+) <$> (+3) <*> (*100) $ 5



(+3)



およびが(*100)



最初に適用され5



、結果は8



およびになり500



ます。次に+



、値8



500



呼び出されます508







次のコードは似ています。



 ghci> (\xyz -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5 [8.0,10.0,2.5]
      
      







ジャズブ\xyz -> [x, y, z]



最終的な実行結果、返された関数(+3)



(*2)



およびで関数呼び出す関数を作成します(/2)



5



3つの関数のそれぞれに渡され、これらの結果で呼び出されます\xyz -> [x, y, z]







インスタンスがどのように機能するかを理解しても問題ありませんその(->) r



ためApplicative



、今理解していない場合でも絶望しないでください。アプリカティブスタイルと関数を試して、関数をアプリカティブファンクタとして使用する方法を理解してください。




圧縮リスト





リストが適用可能なファンクターになる他の方法があることがわかります。既に1つの方法を検討<*>



しました。関数のリストと値のリストを使用した呼び出しです。これは、左側のリストから右側のリストの値に関数を適用するすべての可能な組み合わせのリストを返します。



私たちがしなければ、例えば[(+3),(*2)] <*> [1,2]



、機能は(+3)



両方に適用される1



2



、この関数は(*2)



また、両方に適用されます1



と、2



4つの要素のリストになりますました、:[4,5,2,4]



ただし、[(+3),(*2)] <*> [1,2]



左側のリストの最初の関数が右側のリストの最初の値に適用され、2番目の関数が2番目の値に適用されるなどの方法でも機能します。これにより、2つの値を持つリストが返されます。[4,4]



。あなたはそれを想像することができ[1 + 3, 2 * 2]



ます。私たちがまだ会っていない



インスタンスApplicative



ZipList



あり、彼は住んでいControl.Applicative



ます。



1つの型が同じ型クラスに対して2つのインスタンスを持つことはできないため、ZipList a



1つの(ZipList)



フィールド(リスト)を持つ1つのコンストラクターが存在する型が導入されました。インスタンスは次のとおりです。



 instance Applicative ZipList where pure x = ZipList (repeat x) ZipList fs <*> ZipList xs = ZipList (zipWith (\fx -> fx) fs xs)
      
      







<*>



最初の関数を最初の値に適用し、2番目の関数を2番目の値に適用します。これはで行いzipWith (\fx -> fx) fs xs



ます。ジョブの性質により、zipWith



最終リストは2つの短いリストと同じ長さになります。



pure



ここも面白いです。彼女は値を取り、その値が単純に無限に繰り返されるリストにそれを置きます。pure "haha"



戻りZipList (["haha","haha","haha"...



ます。このpure



値を返す最小のコンテキストに値を配置する必要があることを学習したため、これは混乱を招く可能性があります。そして、あなたは何かの無限のリストがほとんど最小限であると思うでしょう。ただし、zipされたリストを使用する場合は、各位置で値を生成する必要があるため、これは理にかなっています。また、以下の法律にも準拠しています。pure f <*> xs



同等である必要がありますfmap f xs



。呼び出しがpure 3



単に返された場合、2つのzipされたリストの結果リストの長さは2つの短いリストの長さなのでZipList [3]



、呼び出しpure (*2) <*> ZipList [1,5,10]



は結果となりZipList [2]



ます。無限のリストを圧縮すると、結果のリストの長さは常に最終リストの長さと等しくなります。



では、zip形式のリストはどのように適用可能なスタイルで機能しますか?見てみましょう。タイプにZipList a



はinstanceがShow



ないため、関数getZipList



使用して圧縮リストから生リストを抽出する必要があります



 ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100] [101,102,103] ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100..] [101,102,103] ghci> getZipList $ max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2] [5,3,3,4] ghci> getZipList $ (,,) <$> ZipList "dog" <*> ZipList "cat" <*> ZipList "rat" [('d','c','r'),('o','a','a'),('g','t','t')]
      
      







機能(,,)



はと同じ\xyz -> (x,y,z)



です。また、関数(,)



はと同じ\xy -> (x,y)



です。




加えて、zipWith



標準ライブラリのような機能を持っているzipWith3



zipWith4



までを7



zipWith



2つのパラメーターを受け取る関数を使用し、2つのリストをそれで固定します。zipWith3



は、3つのパラメーターを受け取り、3つのリストをそれで固定する関数を取ります。適用スタイルで固定リストを使用する場合、互いに固定するリストの数ごとに個別の固定機能を用意する必要はありません。関数を使用して、applicativeスタイルを使用して任意の数のリストを互いに固定するだけです。これは非常に便利です。



適用法





通常のファンクターのように、適用ファンクターにはいくつかの法則があります。最も重要な法律は実施されることpure f <*> x = fmap fx



です。演習として、この章の一部の適用ファンクターについて、この法則が満たされていることを証明できます。その他の適用法は以下のとおりです。







多くのページを必要とし、やや退屈になるので、それらを詳細に検討しません。興味がある場合は、それらをよりよく知り、いくつかのインスタンスで実行されるかどうかを確認できます。



Applicative Functorを操作するための便利な関数





Control.Applicative



呼び出されliftA2



、次のタイプの関数を定義します



 liftA2 :: (Applicative f) => (a -> b -> c) -> fa -> fb -> fc
      
      







次のように定義されます。



 liftA2 :: (Applicative f) => (a -> b -> c) -> fa -> fb -> fc liftA2 fab = f <$> a <*> b
      
      







これは、2つの適用可能な値の間に関数を適用するだけで、説明した適用可能なスタイルを非表示にします。ただし、適用ファンクタが通常のファンクタよりも強力である理由を明確に示しています。



通常のファンクターを使用する場合、関数を使用して単一のファンクター値を簡単に表示できます。適用可能なファンクターを使用する場合、ファンクターの複数の値の間に関数を適用できます。フォームでこの関数のタイプを知覚するのもおもしろい(a -> b -> c) -> (fa -> fb -> fc)



です。このように認識した場合liftA2



、通常のバイナリ関数を取り、2つの適用可能な値で機能する関数に変換すると言うことができます。



興味深い概念があります。2つの適用可能な値を取得し、それらを1つの適用可能な値に結合できます。この値には、リスト内のこれら2つの適用可能な値の結果が含まれます。たとえば、とがJust 3



ありJust 4



ます。2番目のファンクターにはシングルトンリストが含まれているとします。これは非常に簡単に実現できるためです。



 ghci> fmap (\x -> [x]) (Just 4) Just [4]
      
      







では、とを持っているJust 3



としましょうJust [4]



どうやって手に入れるのJust [3,4]



簡単です:



 ghci> liftA2 (:) (Just 3) (Just [4]) Just [3,4] ghci> (:) <$> Just 3 <*> Just [4] Just [3,4]
      
      







:



これは、要素とリストを受け取り、その要素を先頭に持つ新しいリストを返す関数であることを思い出してください。これでJust [3,4]



、これと組み合わせJust 2



てプロデュースできJust [2,3,4]



ますか?はい、できます。任意の数の適用可能な値を1つの適用可能な値に結合できるようです。この値には、これらの適用可能な値の結果のリストが含まれています。



applicative値のリストを受け取り、その結果値としてリストを含むapplicative値を返す関数を実装してみましょう。私たちは彼女に電話しsequenceA



ます:



 sequenceA :: (Applicative f) => [fa] -> f [a] sequenceA [] = pure [] sequenceA (x:xs) = (:) <$> x <*> sequenceA xs
      
      







ああ、再帰!まず、タイプを見てみましょう。適用可能な値のリストをリスト付きの適用可能な値に変換します。その後、ベースケースの基盤を構築できます。空のリストを結果のリストを含む適用可能な値に変換する場合は、空のリストをデフォルトのコンテキストに配置するだけです。再帰が登場します。ヘッドとテールを持つリストがある場合(x



これは適用可能な値でありxs



、それらで構成されるリストであることを思い出してください)、sequenceA



テールを呼び出します。テールは、その中にリストを持つ適用可能な値を返します。その後、単にapplicative value内に含まれる値x



、このapplicative value内のリストに先行します、それだけです!



これを行うと仮定します。



 sequenceA [Just 1, Just 2]
      
      







定義により、これは次と同等です。



 (:) <$> Just 1 <*> sequenceA [Just 2]
      
      







これをさらに破ると、次のようになります。



 (:) <$> Just 1 <*> ((:) <$> Just 2 <*> sequenceA [])
      
      







呼び出しsequenceA []



がform Just []



終了することがわかっているため、この式は次のようになります。



 (:) <$> Just 1 <*> ((:) <$> Just 2 <*> Just [])
      
      







これはこれに似ています:



 (:) <$> Just 1 <*> Just [2]
      
      







何が等しいJust [1,2]



これ



を実装する別の方法sequenceA



は、畳み込みを使用することです。リストを要素ごとに調べ、途中で結果を蓄積するほとんどすべての関数は、畳み込みを使用して実装できることを思い出してください。



 sequenceA :: (Applicative f) => [fa] -> f [a] sequenceA = foldr (liftA2 (:)) (pure [])
      
      







バッテリー値から始めて、リストを最後から順に見ていきますpure []



liftA2 (:)



バッテリーとリストの最後の要素の間で使用します。これにより、シングルトンリストを含む適用可能な値が得られます。次にliftA2 (:)



、現在の最後の要素と現在のバッテリーなどを使用して、すべての適用可能な値の結果のリストを含むバッテリーのみを取得します。



関数をいくつかの適用可能な値に適用してみましょう。



 ghci> sequenceA [Just 3, Just 2, Just 1] Just [3,2,1] ghci> sequenceA [Just 3, Nothing, Just 1] Nothing ghci> sequenceA [(+3),(+2),(+1)] 3 [6,5,4] ghci> sequenceA [[1,2,3],[4,5,6]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] ghci> sequenceA [[1,2,3],[4,5,6],[3,4,4],[]] []
      
      







値を使用する場合Maybe



sequenceA



それは値作成Maybe



自体の中でリストとしてすべての結果を含んでいます。値のいずれかが等しいNothing



場合、結果もNothing



です。これは、値のリストがあり、値Maybe



が等しくない場合にのみ値に関心がある場合に便利ですNothing







関数に適用されると、関数sequenceA



のリストを受け取り、リストを返す関数を返します。この例では、パラメーターとして数値を受け取り、リスト内の各関数に適用する関数を作成し、結果のリストを返しました。sequenceA [(+3),(+2),(+1)] 3



原因となる(+3)



パラメータ3



(+2)



-パラメータを3



、そして(+1)



オプションで-3



、これらの結果をすべてリストとして返します。



実行すると、(+) <$> (+3) <*> (*2)



パラメータをとる関数を作成し、それを送信します(+3)



し、(*2)



その後、原因+



これら二つの結果を。同じように、sequenceA [(+3),(*2)]



パラメータを取得してリスト内のすべての関数に渡す関数を作成することは理にかなっています。+



関数の結果で呼び出す代わりに、組み合わせを:



使用pure []



して、これらの結果をこの関数の結果であるリストに蓄積します。



使用するsequenceA



関数のリストがあり、それらにすべて同じ入力を与え、結果のリストを表示したい場合に便利です。たとえば、数値があり、リスト内のすべての述部を満たすかどうか疑問に思っています。これを行う1つの方法を次に示します。



 ghci> map (\f -> f 7) [(>4),(<10),odd] [True,True,True] ghci> and $ map (\f -> f 7) [(>4),(<10),odd] True
      
      







それを思い出して、型の値のリストを取り、それらがすべて等しい場合にBool



戻ります。同じ結果を達成する別の方法は、以下を使用することです:True



True



sequenceA







 ghci> sequenceA [(>4),(<10),odd] 7 [True,True,True] ghci> and $ sequenceA [(>4),(<10),odd] 7 True
      
      







sequenceA [(> 4)、(<10)、odd]は、数値を取り、それをのすべての述語に渡す関数を作成し、[(>4),(<10),odd]



ブール値のリストを返します。タイプのリストをタイプ(Num a) => [a -> Bool]



の関数に変え(Num a) => a -> [Bool]



ます。とてもクールですね



リストは同種なので、リスト内のすべての関数はもちろん同じ型でなければなりません。のようなリストを取得することはできません[ord, (+3)]



。なぜならord



、文字を受け取って数字を返すのに対し(+3)



、数字を受け取って数字を返すからです。



とともに使用すると[]



sequenceA



リストのリストを取り、リストのリストを返します。実際、リスト内にある要素のすべての組み合わせを含むリストを作成します。説明のために、ここに使用した前の例を示しますsequenceA



リストジェネレーターを使用して実行します。



 ghci> sequenceA [[1,2,3],[4,5,6]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] ghci> [[x,y] | x <- [1,2,3], y <- [4,5,6]] [[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]] ghci> sequenceA [[1,2],[3,4]] [[1,3],[1,4],[2,3],[2,4]] ghci> [[x,y] | x <- [1,2], y <- [3,4]] [[1,3],[1,4],[2,3],[2,4]] ghci> sequenceA [[1,2],[3,4],[5,6]] [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]] ghci> [[x,y,z] | x <- [1,2], y <- [3,4], z <- [5,6]] [[1,3,5],[1,3,6],[1,4,5],[1,4,6],[2,3,5],[2,3,6],[2,4,5],[2,4,6]]
      
      







(+) <$> [1,2] <*> [4,5,6]



これは、非決定論的計算の結果を返しx + y



x



からそれぞれの値をとるが[1,2]



、およびy



からの各値をとります[4,5,6]



。すべての可能な結果を​​含むリストでこれを提示します。同様に、を実行するsequenceA [[1,2],[3,4],[5,6]]



と、結果は非決定的計算になり[x,y,z]



x



から各値を取得し[1,2]



、のy



各値を取得します[3,4]



。この非決定的計算の結果を表すために、リスト内の各項目が1つの可能なリストであるリストを使用します。そのため、結果はリストのリストになります。



I / Oアクションで使用する場合、sequenceA



これは次と同じですsequence



! I / Oアクションのリストを受け取り、これらの各アクションを実行するI / Oアクションを返します。結果として、これらのI / Oアクションの結果のリストが含まれます。これは、値をvalueに[IO a]



変換するIO [a]



ために、実行時に結果のリストを返すI / Oアクションを作成するために、これらすべてのI / Oアクションを順番に配置して、実行結果が必要なときに1つずつ実行できるようにする必要があるためです。実行しないと、I / Oアクションの結果を取得できません。



3つのI / OアクションgetLine



をシーケンスに入れましょう



 ghci> sequenceA [getLine, getLine, getLine] heyh ho woo ["heyh","ho","woo"]
      
      







結論として、適用ファンクターは興味深いだけでなく、有用でもあります。I / O計算、非決定的計算、失敗する可能性のある計算など、さまざまな計算をapplicativeスタイルを使用して組み合わせることができます。ただ、使用<$>



して<*>



、私たちは一様にapplicativesの任意の数で動作し、それらのそれぞれのセマンティクスを活用するために通常の機能を使用することができます。




完全な処理(構造とコンテンツ)を必要とする本の前の章の翻訳:

  1. はじめに
  2. 開始する
  3. タイプとタイプクラス
  4. 関数の構文
  5. 再帰
  6. 高階関数
  7. モジュール
  8. 独自の型と型クラスを作成する
  9. 入出力
  10. 機能的な問題解決




出版の次の行はモノイドの章ですこれまでのところ、内容を改善する建設的なコメントを期待しています。よろしくお願いします!



All Articles