第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幕

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
です。演習として、この章の一部の適用ファンクターについて、この法則が満たされていることを証明できます。その他の適用法は以下のとおりです。
-
pure id <*> v = v
-
pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
-
pure f <*> pure x = pure (fx)
-
u <*> pure y = pure ($ y) <*> u
多くのページを必要とし、やや退屈になるので、それらを詳細に検討しません。興味がある場合は、それらをよりよく知り、いくつかのインスタンスで実行されるかどうかを確認できます。
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の任意の数で動作し、それらのそれぞれのセマンティクスを活用するために通常の機能を使用することができます。
完全な処理(構造とコンテンツ)を必要とする本の前の章の翻訳:
出版の次の行はモノイドの章です。これまでのところ、内容を改善する建設的なコメントを期待しています。よろしくお願いします!