如何(fmap.Fmap)类型

问题描述 投票:19回答:3

我一直在阅读一篇文章(http://comonad.com/reader/2012/abstracting-with-applicatives/)并在那里找到以下代码片段:

newtype Compose f g a = Compose (f (g a)) deriving Show

instance (Functor f, Functor g) => Functor (Compose f g) where
    fmap f (Compose x) = Compose $ (fmap . fmap) f x

实际上(fmap . fmap)怎么样?

他们的类型是:

(.)  :: (a -> b) -> (r -> a) -> (r -> b)
fmap :: (a -> b) -> f a -> f b
fmap :: (a -> b) -> f a -> f b

现在从这里我可以看出fmap . fmap将不会进行类型检查?

haskell functor
3个回答
26
投票

首先让我们将类型变量的名称更改为唯一:

(.)  :: (a -> b) -> (r -> a) -> (r -> b)
fmap :: Functor f => (c -> d) -> f c -> f d
fmap :: Functor g => (x -> y) -> g x -> g y

现在.的第一个参数有a -> b类型,我们提供(c -> d) -> (f c -> f d)类型的参数,所以ac -> dbf c -> f d。所以到目前为止我们有:

(.) :: Functor f => -- Left operand
                    ((c -> d) -> (f c -> f d)) ->
                    -- Right operand
                    (r -> (c -> d)) ->
                    -- Result
                    (r -> (f c -> f d))

.的第二个参数有类型r -> a a.k.a.r -> (c -> d),我们给出的参数有(x -> y) -> (g x -> g y)类型,所以r变成x -> yc变成g xd变成g y。所以现在我们有:

(.)       :: (Functor f, Functor g) => -- Left operand
                                       ((g x -> g y) -> (f (g x) -> f (g y))) -> 
                                       -- Right operand
                                       ((x -> y) -> (g x -> g y)) ->
                                       -- Result
                                       (x -> y) -> f (g x) -> f (g y)
fmap.fmap :: (Functor f, Functor g) => (x -> y) -> f (g x) -> f (g y)

20
投票

表达式fmap . fmap有两个fmap实例,原则上它们可以有不同的类型。所以让我们说他们的类型是

fmap :: (x -> y) -> (g x -> g y)
fmap :: (u -> v) -> (f u -> f v)

我们的工作是统一类型(相当于这些类型变量之间的相等关系),以便第一个fmap的右侧与第二个fmap的左侧相同。希望你能看到,如果你设置u = g xv = g y,你最终会得到

fmap :: (  x ->   y) -> (   g x  ->    g y )
fmap :: (g x -> g y) -> (f (g x) -> f (g y))

现在组成的类型是

(.) :: (b -> c) -> (a -> b) -> (a -> c)

为了解决这个问题,您可以选择a = x -> yb = g x -> g y以及c = f (g x) -> f (g y)以便可以编写类型

(.) :: ((g x -> g y) -> (f (g x) -> f (g y)))    ->    ((x -> y) -> (g x -> g y))    ->    ((x -> y) -> (f (g x) -> f (g y)))

这是非常笨拙的,但它只是(.)的原始类型签名的专业化。现在你可以检查一切是否匹配,以便fmap . fmap typechecks。


另一种方法是从相反的方向接近它。例如,假设你有一些具有两层functoriality的对象

>> let x = [Just "Alice", Nothing, Just "Bob"]

并且你有一些功能可以将爆炸添加到任何字符串

bang :: String -> String
bang str = str ++ "!"

你想将爆炸添加到x中的每个字符串中。你可以从String -> StringMaybe String -> Maybe String与一级fmap

fmap bang :: Maybe String -> Maybe String

你可以和[Maybe String] -> [Maybe String]的另一个应用去fmap

fmap (fmap bang) :: [Maybe String] -> [Maybe String]

这样做我们想要的吗?

>> fmap (fmap bang) x
[Just "Alice!", Nothing, Just "Bob!"]

让我们写一个实用函数fmap2,它接受任何函数f并将fmap应用于它两次,这样我们就可以编写fmap2 bang x了。这看起来像这样

fmap2 f x = fmap (fmap f) x

你当然可以从两边放下x

fmap2 f = fmap (fmap f)

现在你意识到g (h x)模式与(g . h) x相同,所以你可以写

fmap2 f = (fmap . fmap) f

所以你现在可以从两边放下f

fmap2 = fmap . fmap

这是你感兴趣的功能。所以你看到fmap . fmap只需要一个函数,并将fmap应用于它两次,这样就可以通过两个级别的functoriality来提升它。


4
投票

老问题,但对我来说,概念上,fmap代表“把a -> b带到'一级',到f a -> f b”。

因此,如果我有一个a -> b,我可以fmap它给我一个f a -> f b

如果我有一个f a -> f b,我可以fmap它再次给我一个g (f a) -> g (f a)。将f a -> f b的功能提升到新的高度 - 一个新的水平。

因此,“fmapping”一次提升功能。 fmapping两次举升提升功能...所以,双提升。

放入haskell语法的语言:

f                    ::         a   ->         b
fmap f               ::       f a   ->       f b
fmap (fmap f)        ::    g (f a)  ->    g (f b)
fmap (fmap (fmap f)) :: h (g (f a)) -> h (g (f b))

请注意每个连续的fmap如何将原来的a -> b提升到另一个新的水平。所以,

fmap               :: (a -> b) -> (      f a  ->        f b  )
fmap . fmap        :: (a -> b) -> (   g (f a) ->     g (f b) )
fmap . fmap . fmap :: (a -> b) -> (h (g (f a)) -> h (g (f a)))

任何返回与其输入相同的arity函数的“高阶函数”都可以做到这一点。以zipWith :: (a -> b -> c) -> ([a] -> [b] -> [c])为例,它接受一个带两个参数的函数并返回一个带有两个参数的新函数。我们可以用同样的方式链接zipWiths:

f                   ::   a   ->   b   ->   c
zipWith f           ::  [a]  ->  [b]  ->  [c]
zipWith (zipWith f) :: [[a]] -> [[b]] -> [[c]]

所以

zipWith           :: (a -> b -> c) -> ( [a]  ->  [b]  ->  [c] )
zipWith . zipWith :: (a -> b -> c) -> ([[a]] -> [[b]] -> [[c]])

liftA2的工作原理大致相同:

f                 ::      a  ->      b  ->      c
liftA2 f          ::    f a  ->    f b  ->    f c
liftA2 (liftA2 f) :: g (f a) -> g (f b) -> g (f c)

在镜头库的现代实现中使用的一个相当令人惊讶的例子是traverse

f                                ::         a   -> IO          b
traverse f                       ::       f a   -> IO (      f b  )
traverse (traverse f)            ::    g (f a)  -> IO (   g (f b) )
traverse (traverse (traverse f)) :: h (g (f a)) -> IO (h (g (f b)))

所以你可以拥有这样的东西:

traverse            :: (a -> m b) -> (   f a  -> m (   f b ))
traverse . traverse :: (a -> m b) -> (g (f a) -> m (g (f b)))
© www.soinside.com 2019 - 2024. All rights reserved.