表达式fmap . fmap 有两个fmap 实例,原则上它们可以有不同的类型。所以假设他们的类型是
fmap :: (x -> y) -> (g x -> g y)
fmap :: (u -> v) -> (f u -> f v)
我们的工作是统一类型(这相当于提出这些类型变量之间的相等关系),以便第一个 fmap 的右侧与第二个 @987654325 的左侧相同@。希望你能看到,如果你设置了u = g x 和v = g y,你最终会得到
fmap :: ( x -> y) -> ( g x -> g y )
fmap :: (g x -> g y) -> (f (g x) -> f (g y))
现在compose的类型是
(.) :: (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 类型检查。
另一种方法是从相反的方向接近它。假设您有一些具有两个功能级别的对象,例如
>> 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
您可以通过fmap的另一个应用程序转到[Maybe String] -> [Maybe String]
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 应用到它两次,这样就可以通过两个层次的函数来提升它。