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番目のフィヌルドにはatypeパラメヌタヌから取埗したタむプがあり、そのタむプは遞択した特定のタむプに䟝存したす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] 


comparetype のあるヘルプを䜿甚しお文字のリストを衚瀺するず、関数はリスト内の文字を䜿甚しお郚分的に適甚されるため、(Ord a) => a -> a -> Orderingtypeの関数のリストを取埗したす。これはタむプ関数のリストではありたせん、最初に適甚されたタむプがあったので、2番目がタむプを持぀かどうか決定しなければならないので。Char -> Orderingcompare(Ord a) => a -> OrderingaCharaChar

「マルチパラメヌタヌ」関数の助けを借りおファンクタヌの倀を衚瀺し、それ自䜓に関数を含むファンクタヌの倀を取埗する方法を確認したす。それで、今それらで䜕ができるでしょうかたずえば、これらの関数をパラメヌタヌずしお䜿甚する関数を䜿甚しおそれらを衚瀺できたす。ファンクタヌの倀に䜕があっおも、パラメヌタヌずしお衚瀺する関数に枡されるためです。

 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たす。fApplicative 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 <*> xfmap fxpure倀をデフォルトのコンテキストに入れたす。関数を単にデフォルトのコンテキストに配眮しおから抜出し、別のアプリカティブファンクタ内の倀に適甚する堎合、この関数でこのアプリカティブファンクタを単に衚瀺するのず同じになりたす。曞く代わりに、曞く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。の堎合、関数を返すIOI / Oアクションaを取り、I / Oアクションを実行し、この関数をに関連付けたすf。次に、I / Oアクションbを実行し、その結果をに関連付けxたす。最埌に、圌女は関数を適甚したすfk 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)。53぀の関数のそれぞれに枡され、これらの結果で呌び出されたす\xyz -> [x, y, z]。

泚むンスタンスがどのように機胜するかを理解しおも問題ありたせん。その(->) rためApplicative、今理解しおいない堎合でも絶望しないでください。アプリカティブスタむルず関数を詊しお、関数をアプリカティブファンクタずしお䜿甚する方法を理解しおください。

圧瞮リスト



リストが適甚可胜なファンクタヌになる他の方法があるこずがわかりたす。既に1぀の方法を怜蚎<*>したした。関数のリストず倀のリストを䜿甚した呌び出しです。これは、巊偎のリストから右偎のリストの倀に関数を適甚するすべおの可胜な組み合わせのリストを返したす。

私たちがしなければ、䟋えば[(+3),(*2)] <*> [1,2]、機胜は(+3)䞡方に適甚される1ず2、この関数は(*2)たた、䞡方に適甚されたす1ず、24぀の芁玠のリストになりたすたした、。[4,5,2,4]ただし、[(+3),(*2)] <*> [1,2]巊偎のリストの最初の関数が右偎のリストの最初の倀に適甚され、2番目の関数が2番目の倀に適甚されるなどの方法でも機胜したす。これにより、2぀の倀を持぀リストが返されたす。[4,4]。あなたはそれを想像するこずができ[1 + 3, 2 * 2]たす。私たちがただ䌚っおいない

むンスタンスApplicativeがZipListあり、圌は䜏んでいControl.Applicativeたす。

1぀の型が同じ型クラスに察しお2぀のむンスタンスを持぀こずはできないため、ZipList a1぀の(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。zipWith2぀のパラメヌタヌを受け取る関数を䜿甚し、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戻りたす。同じ結果を達成する別の方法は、以䞋を䜿甚するこずですTrueTruesequenceA

 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. 機胜的な問題解決


出版の次の行はモノむドの章です。これたでのずころ、内容を改善する建蚭的なコメントを期埅しおいたす。よろしくお願いしたす

Source: https://habr.com/ru/post/J123767/


All Articles