fugafuga.write

日々のログ

すごいH本 part71

アプリカティブファンクター

ファンクターとは関数で写せるもののこと。リスト、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 と同じ。 (->)ra の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 ar -> 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"

関数合成できている。

関数ファンクター(->) rr型の入力を適用すれば結果が返ってくるという文脈を表すことができる。 より具体化した(->) r ar型の入力を適用すれば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)に適用するファンクター値によって動きが変わっていることがわかる。

所感

関数の持ち上げはなんのためにやってんのかわからん。 アプリカティブファンクターはいまだ登場せず。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

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