如何(fmap.fmap)typechecks

时间:2014-04-12 12:52:57

标签: haskell functor

我一直在阅读一篇文章(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将无法进行类型检查?

3 个答案:

答案 0 :(得分: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 -> d和{{1}是b。所以到目前为止我们有:

f 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,我们提供的参数类型为r -> (c -> d),因此(x -> y) -> (g x -> g y)变为rx -> y变为cg x变为d。所以现在我们有:

g y

答案 1 :(得分: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类型检查。


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

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

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

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

您想将爆炸添加到x中的每个字符串中。您可以使用String -> String

的一个级别从Maybe 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来提升它。 / p>

答案 2 :(得分: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]),它接受​​一个带两个参数的函数,并返回一个带两个参数的新函数。我们可以用同样的方式链接zipWith

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)))