2012年12月17日月曜日

HaskellのFunctorのおさらい

Functorってなに?

Functorとは「ある関数で値を写して新しい値を作る」型クラスらしい、数学的には「関手」とか言うらしいが、まぁ「写像」程度の理解度でいいのか。
ApplicativeやらMonadやらでつまずくので、Functorは理解したい、なので...

Functor調べる

とりあえず、Functorクラスの情報を調べてみる、Functorとは型クラスでfmapって言う関数が定義されている
Prelude> :i Functor
class Functor f where
  fmap :: (a -> b) -> f a -> f b
  (GHC.Base.<$) :: a -> f b -> f a
   -- Defined in `GHC.Base'
instance Functor Maybe -- Defined in `Data.Maybe'
instance Functor [] -- Defined in `GHC.Base'
instance Functor IO -- Defined in `GHC.Base'
Prelude>

そしての「Maybe」とか「[]」がFunctorのインスタンスになっているのがわかる


fmapの使い方

実際に使ってみる
Prelude> fmap (+10) [1,2,3,4,5]
[11,12,13,14,15]
Prelude> fmap (+10) (Just 20)
Just 30
Prelude> fmap (+10) Nothing
Nothing
Prelude>

簡単な使い方はこんな感じ、fmapの動作は
  • [] の場合、配列のそれぞれの要素に(+10)を適用して新しい配列[11,12,13,14,15]を返してくる
  • Maybeの場合、Just (20 +(10))、要するにfmapがJustの中身に(+10)を適用して返してくる
要するに、「fmap :: (a -> b) -> f a -> f b」を直訳すると

「型aから型bに変換する関数に、型aのファンクタ値を適用して、型bのファンクタ値を生成する」

ってことか


自分の型をFunctorクラスのインスタンスにしてみる

実際に自分の型を作成してFunctorクラスのインスタンスにしてみる。ThisTypeデータ型を宣言してGHCiに読み込ませてみる

FuncSample.hs
data ThisType a = ThisType a deriving(Eq, Show)

GHCiを起動してThisType型の動作を確認する
Prelude> :l FuncSample
[1 of 1] Compiling Main             ( FuncSample.hs, interpreted )
Ok, modules loaded: Main.
*Main> :i ThisType
data ThisType a = ThisType a  -- Defined at FuncSample.hs:1:6
instance Eq a => Eq (ThisType a) -- Defined at FuncSample.hs:1:37
instance Show a => Show (ThisType a)
  -- Defined at FuncSample.hs:1:41
*Main> :t ThisType
ThisType :: a -> ThisType a
*Main> ThisType 20
ThisType 20
*Main> ThisType "ABC"
ThisType "ABC"
*Main>

Functorとして使ってみると、まずThisType型がFunctor型クラスのインスタンスとして宣言されていないエラーと怒られる、なるほど、ではFunctorのインスタンスにしてみる
*Main> fmap (+10) (ThisType 20)

<interactive>:19:1:
    No instance for (Functor ThisType)
      arising from a use of `fmap'
    Possible fix: add an instance declaration for (Functor ThisType)
    In the expression: fmap (+ 10) (ThisType 20)
    In an equation for `it': it = fmap (+ 10) (ThisType 20)
*Main>

FuncSample.hsにinstanceを追加
data ThisType a = ThisType { getThisType :: a } deriving(Eq, Show)
instance Functor ThisType where
    fmap f (ThisType x) = ThisType (f x)

main = do
       let val1 = getThisType $ fmap (+10) (ThisType 20)
           val2 = getThisType $ fmap ("#A君"++) (ThisType "メリーゴーランドに乗る")
       putStrLn $ show(val1)
       putStrLn $ val2

もう一度同じ計算をやらせてみると
~ $ runhaskell FuncSample.hs
30
#A君メリーゴーランドに乗る
~ $

#A君もメリーゴーランドに乗れる


ThisTypeは型コンストラクタ

:k でThisTypeを調べてみると、ThisTypeは型引数を1つ取る型コンストラクタでMaybeとかと同じ事が分かります。Functor型クラスのインスタンスにしたい場合は型引数を1つ取る型コンストラクタでなければならないということ。
*Main>
*Main> :k ThisType
ThisType :: * -> *
*Main> :k Maybe
Maybe :: * -> *
*Main> :k []
[] :: * -> *
*Main>


ThisTypeはFunctor則を満たしてるのか?

これが正確かどうか分からないがThisType型がFunctor則を満たしているか確認する

以下のFunctor則の二つ
  • 第1法則 fmap id = id
  • 第2法則 fmap (f . g) x = fmap f (fmap g x)
Functor則を満たしているかは上の2式を満たしているかどうかで分かるらしい

第1法則 fmap id = id
fmap id = idを満たしているか確認する、多分これはカリー化されているので書き直すとこんな感じか
 fmap id (f a) = id (f a) これは
*Main> fmap id (ThisType 20) == id (ThisType 20)
True
*Main>
とりあえず満たしている

第2法則 fmap (f . g) x = fmap f (fmap g x)これは、関数fと関数gの合成関数とファンクター値にfmapを適用したものと、最初に関数gとfmapを適用し生成したファンクター値をさらに関数fでfmapしたファンクター値が等しくなればいいということ

f = (+10)、g = (+50)として試してみる
*Main> let f=(+10)
*Main> let g=(+50)
*Main> fmap (f . g) (ThisType 20) == fmap f (fmap g (ThisType 20))
True
*Main>

これも良さそう、これでThisTypeはFunctorでしょう(微妙か...)


Functorの定義

ちなみにこの書き方でもできました

FuncSample.h
{-# LANGUAGE DeriveFunctor #-}
data ThisType a = ThisType { getThisType :: a } deriving(Eq, Show, Functor)
  • ファイルの最初に{-# LANGUAGE DeriveFunctor #-}を書く
  • derivingにFunctorを追加する

実行してみると
*Main> fmap id (ThisType 20) == id (ThisType 20)
True
*Main> fmap ((+10) . (+50)) (ThisType 20) == fmap (+10) (fmap (+50) (ThisType 20))
True
*Main>

同じ結果となる、こういう理解でいいと思うが間違いがあったら指摘してください、ヨロピク

0 件のコメント:

コメントを投稿