アプリカティブファンクター
ファンクターとは関数で写せるもののこと。リスト、Maybe、木など。
*Main Lib> :i Functor class Functor (f :: * -> *) where fmap :: (a -> b) -> f a -> f b (<$) :: a -> f b -> f a {-# MINIMAL fmap #-} -- Defined in ‘GHC.Base’ instance Functor (Either a) -- Defined in ‘Data.Either’ instance Functor [] -- Defined in ‘GHC.Base’ instance Functor Maybe -- Defined in ‘GHC.Base’ instance Functor IO -- Defined in ‘GHC.Base’ instance Functor ((->) r) -- Defined in ‘GHC.Base’ instance Functor ((,) a) -- Defined in ‘GHC.Base’
ファンクターは文脈を持った値だとみなすことができる。文脈とは、
- Maybe の場合、
計算が失敗したかもしれない
という文脈 - リストの場合、
複数の値を同時にとるかもしれない
という文脈
fmap は文脈を保ったまま関数を値に適用する。
ファンクターとしてのI/Oアクション
I/Oアクションは足の生えた箱。よって、IOがファンクターの一種であることが理解できる。
IOのFunctorのインスタンスの実装を見る
instance Functor IO where fmap f action = do result <- action return (f result)
I/O アクションに fmap 適用するとこうなる
main = do line <- fmap reverse getLine putStrLn $ "You said " ++ line ++ " backwords!" putStrLn $ "Yes, you said " ++ line ++ " backwords!"
※ main = do line <- ...
のように do
の右側に続けて書くとエラーとなった。
実行
> ./p229_IOfmap blah You said halb backwords! Yes, you said halb backwords!
Maybe や [] のように箱に入っている値と同じようにIOにも関数を適用できる。
この場合の fmap は fmap :: (a -> b) -> IO a -> IO b
という型になる。
何らかの関数に渡すためだけにI/Oの結果に名前を付けているような箇所があったら、 fmap を使うのがよい。
ファンクターの中身を1つでは無く複数の関数を使って写したい場合、 関数合成するのが良い。
import Data.Char import Data.List main = do line <- fmap (intersperse '-' . reverse . map toUpper) getLine putStrLn line
実行
> ./fmapping_io hello there E-R-E-H-T- -O-L-L-E-H
ファンクターとしての関数
Functor のインスタンスの1つに (->) r a
がある。
これは関数の型を表現するときに使う r -> a
と同じ。
(->)
は r
と a
の2つの型引数をとる型コンストラクタといえる。
Either a b
と同じ。
Functor のインスタンスにする型コンストラクタは引数が1つでないといけない。
よって、(->)
に部分適用して (->) r
とすることで Functor のインスタンスになれる。
Control.Monad.Instances
にあるインスタンス宣言は以下のようになっている。
instance Functor ((->) r) where fmap f g = (\x -> f (g x))
関数をファンクターのインスタンスにしたときの fmap は以下のようになる。
fmap :: (a -> b) -> ((->) r a) -> ((->) r b)
さらに (->) r a
を r -> a
のように書き換える
fmap :: (a -> b) -> (r -> a) -> (r -> b)
引数を1つとる関数を2つとって新しい関数を返している。 これは関数合成と同じ。
インスタンス宣言はこうも書ける
instance Functor ((->) r) where fmap = (.)
関数ファンクターを使ってみる
*Main Lib> :m + Control.Monad.Instances <interactive>:1:1: warning: [-Wdeprecations] Module ‘Control.Monad.Instances’ is deprecated: This module now contains no instances and will be removed in the future *Main Lib Control.Monad.Instances> :t fmap (*3) (+100) fmap (*3) (+100) :: Num b => b -> b *Main Lib Control.Monad.Instances> fmap (*3) (+100) 1 303 *Main Lib Control.Monad.Instances> (*3) `fmap` (+100) $ 1 303 *Main Lib Control.Monad.Instances> (*3) . (+100) $ 1 303 *Main Lib Control.Monad.Instances> fmap (show . (*3)) (+100) 1 "303"
関数合成できている。
関数ファンクター(->) r
はr型の入力を適用すれば結果が返ってくる
という文脈を表すことができる。
より具体化した(->) r a
はr型の入力を適用すればa型の結果が返ってくる
という文脈を表している。
カリー化の観点から fmap を見る、
func :: a -> b -> c func :: a -> (b -> c)
と同じように fmap を考えると、
fmap :: (a -> b) -> f a -> f b fmap :: (a -> b) -> (f a -> f b)
と考えることができる。
これは、関数をとって元の関数に似ているけどファンクター値をとってファンクター値を返す関数
を返す関数と考えることができる。
このような操作を関数の持ち上げ(lifting)
という。
*Main Lib> :t fmap (*2) fmap (*2) :: (Num b, Functor f) => f b -> f b *Main Lib> :t fmap (replicate 3) fmap (replicate 3) :: Functor f => f a -> f [a] *Main Lib> :set -XNoMonomorphismRestriction *Main Lib> let shout = fmap (++"!") *Main Lib> :t shout shout :: Functor f => f [Char] -> f [Char] *Main Lib> shout ["ha","ka","ta","no"] ["ha!","ka!","ta!","no!"]
fmap については、2通りの考え方ができる。
- fmap は関数とファンクター値をとって、その関数でファンクター値を写して返す関数
- fmap は値から値への関数をとって、それをファンクター値からファンクター値への関数に持ち上げたものを返す関数
*Main Lib> fmap (replicate 3) [1,2,3,4] [[1,1,1],[2,2,2],[3,3,3],[4,4,4]] *Main Lib> fmap (replicate 3) (Just 4) Just [4,4,4] *Main Lib> fmap (replicate 3) (Right "blah") Right ["blah","blah","blah"] *Main Lib> fmap (replicate 3) Nothing Nothing *Main Lib> fmap (replicate 3) (Left "foo") Left "foo"
fmap (replicate 3)
に適用するファンクター値によって動きが変わっていることがわかる。
所感
関数の持ち上げはなんのためにやってんのかわからん。 アプリカティブファンクターはいまだ登場せず。

- 作者: MiranLipovaca
- 出版社/メーカー: オーム社
- 発売日: 2017/07/14
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る