第十三章:應用函子

夢飛發表於2017-03-04

作為函子的子類型別,應用函子概括了某些函子的額外特性:把函子裡的函式作用到函子裡的值。

1.函子的侷限

  • 函子抽象提供了fmap函式,用來把普通函式升格成可以操作函子容器的函式。例如我們可以把所有能出來a型別的函式升格成處理 Maybe a型別的函式

  • 現假設有一個a->b->c型別的函式,我們可以繼續使用fmap。通過構造replicateB這個函式,使得fmap replicateB的型別變成了Maybe Int->Maybe String

    a :: Maybe Int
    a = Just 3
    
    
    b :: Char
    b = 'x'
    
    
    replicate :: Int -> b -> [b]
    
    
    replicateB :: Int -> String
    replicateB = \x -> replicate x b
    
    
    fmap replicateB a
    -- Just "xxx"
    
  • 而實際遇到的問題往往是,參與計算的引數裡不只有一個是包裹在函子型別裡面的。如上面的例子中Char型別的值也包裹在Maybe裡呢?

    --模式匹配
    replicateMaybe :: Maybe Int -> Maybe a -> Maybe [a]
    replicateMaybe (Just n) (Just a) = Just $ replicate n a
    replicateMaybe _ Nothing = Nothing
    replicateMaybe Nothing _ = Nothing
    
    
    --升格雙引數函式的高階函式liftMaybe2
    liftMaybe2 :: (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
    liftMaybe2 f (Just x) (Just y) = Just $ f x y
    liftMaybe2 _ _ _               = Nothing
    

    同理可以得到liftMayben,但是這麼做需要為每一種引數數量的情況寫一個特例且無法使用別的型別的函子

  • 一個觀察——如果多引數函式只經過fmap的升格,並部分應用一個包裹在函子中的值,將會得到一個包裹在函子中的函式

    replicate :: Int -> a -> [a]
    replicate :: Int -> (a -> [a])
    
    
    fmap replicate :: f Int -> f (a -> [a])
    fmap replicate (Just 3) :: Maybe (a -> [a])
    

    於是

    replicateThreeF :: Maybe (a -> [a])
    replicateThreeF = fmap replicate (Just 3)
    
    
    applyMaybe :: Maybe (a -> b) -> Maybe a -> Maybe b
    applyMaybe (Just f) (Just x) = Just $ f x
    applyMaybe _ _               = Nothing
    
    
    applyMaybe replicateThreeF (Just 'x')
    -- Just "xxx"
    
    
    --升格任意引數數量的函式到Maybe引數型別
    addAll :: Int -> Int -> Int -> Int
    addAll x y z = x + y + z
    
    
    (fmap addAll $ Just 1) `applyMaybe` Just 2 `applyMaybe` Just 3
    
  • 我們可否抽象出一個適用於任意型別函子f上的函式呢?

    (<*>) :: Functor f => f (a -> b) -> f a -> f b
    (<*>) = ???
    

    +對於列表

    (<*>) ::  [(a -> b)] -> [a] -> [b]
    fs <*> xs = concat $ map (\f -> map f xs) fs 
    
    
    --concat把列表裡的所有列表連線起來
    concat :: [[a]] -> [a]
    concat xss = foldl (++) [] xss
    

    +上面的問題答案是不可以

    (<*>) :: Const a (b -> c) -> Const a b -> Const a c
    cf <*> cx = ???
    

2.什麼是函子

我們把剛剛希望被抽象出來的、能夠提供<*>定義的函子稱為 應用函子 applicative functor

  • Applicative 型別代表的就是這一型別

    class Functor f => Applicative f where
         pure :: a -> f a
         <*> :: f (a -> b) -> f a -> f b
    

    +pure接收一個引數並把它包裹到函子裡。我們把這個升格值的過程叫**調價最小上下文**minimum context +<*>是升格計算的核心——函子應用運算子

    +pure和<*>必須滿足四個條件:單位律,組合律,同態律和互換律

  • Reader應用函子 (->) a

    pure :: x -> (a -> x)
    (<*>) :: (a -> (x -> y)) -> (a -> x) -> (a -> y)
    
    
    instance Applicative ((->) a) where
        pure x = \_ -> x
        --pure = const
    
    
    (<*>) :: (a -> (x -> y)) -> (a -> x) -> (a -> y)
    fxy <*> fx = \a -> fxy a $ fx a
    

    +這個函子常用在配置模組化的問題上

-自然升格

自然升格指的是使用應用函子構建運算的一種書寫習慣

  • <$>函式是fmap的中綴版本。和$不同的是,<$>是一個左結合的函式,在自然升格裡的作用是把一個函式先升格至函子的範疇,然後就可以方便的使用<*>去繼續應用計算

    (<$>) :: (a -> b) -> f a -> f b
    f <$> x = fmap f x
    -- <$> = fmap
    
    
    infixl 4 <$>
    
  • 編譯器自動根據引數型別推匯出了我們需要的應用函子型別,形如

    ... <$> ... <*> ... <*> ...
    

    這種寫法就叫自然升格

  • 模組Data.Functor中,提供了自然升格寫法中需要的兩個中綴函式 <$ 和 $>

  • 有時,我們希望直接使用某個包裹在函子的值填充到生成的函子中

  • Control.Applicative中 的* > 和<*
  • 引數從1到3的常用升格函式 liftA, liftA2, liftA3

3.IO應用函子

  • IO應用函子包裹的是和系統輸入/輸出相關的值

  • 它的作用就是保證和外界的互動

相關文章