我一直在阅读一篇文章(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
将无法进行类型检查?
答案 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)
类型的参数,因此a
为c -> 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)
变为r
,x -> y
变为c
,g 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 x
和v = 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 -> y
和b = 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)))