我正在尝试理解一些Haskell代码。
这是有道理的。
Prelude> fmap (+1) (Just 1)
Just 2
这也是有道理的。
Prelude> (fmap.fmap) (+1) (Just [1])
Just [2]
但我不明白这是如何工作的。
Prelude> (fmap.fmap) (+1) Just 1
Just 2
我试过把零件弄出来。在我看来,这就是正在发生的事情。
(fmap (fmap (+1)) Just) 1
我尝试输入子表达式。
这是有道理的。
Prelude> :t fmap (+1)
fmap (+1) :: (Functor f, Num b) => f b -> f b
这还是有道理的。
Prelude> :t fmap (fmap (+1))
fmap (fmap (+1)) :: (Functor f, Functor f1, Num b) =>
f (f1 b) -> f (f1 b)
但我不明白这一点。
Prelude> :t fmap (fmap (+1)) Just
fmap (fmap (+1)) Just :: Num b => b -> Maybe b
带有类型的函数是如何做到
的(Functor f, Functor f1, Num b) => f (f1 b) -> f (f1 b)
应用具有类型的Just
后:
a -> Maybe a
结果是这种类型?
Num b => b -> Maybe b
关于函数作为 Haskell 中函子实例的问题可能与此有关,但我仍然感到困惑。
发生的事情f
解析为函子(->) a
,f1
解析为Maybe
,因为
Just :: (->) a (Maybe a)
因此,如果我们使用上述绑定编写fmap (fmap (+1))
类型,我们会得到:
fmap (fmap (+1)) :: Num b => (->) a (Maybe b) -> (->) a (Maybe b)
将(->)
重写为中缀构造函数,我们得到:
fmap (fmap (+1)) :: Num b => (a -> Maybe b) -> (a -> Maybe b)
现在我们将其应用于Just :: a -> Maybe a
因此我们得到
fmap (fmap (+1)) Just :: Num a => a -> Maybe a
你写Just 1
而不是(Just 1)
因此,这是两个单独的参数。我们现在可以用更规范的形式重写它,例如:
(fmap . fmap) (+1) Just 1
-> (x -> fmap (fmap x)) (+1) Just 1
-> ((fmap (fmap (+1)) Just) 1
所以现在我们可以分析类型:
fmap1:: Functor f => (a -> b) -> f a -> f b
fmap2:: Functor g => (c -> d) -> g c -> g d
(+1) :: Num h => h -> h
Just :: i -> Maybe i
1 :: Num j => j
其中fmapi
是表达式中的第i个fmap
(如果我们从左到右阅读)。如果我们知道执行一些分析,我们会发现,既然我们使用fmap (+1)
,我们知道c ~ d ~ h
:
fmap1:: Functor f => (a -> b) -> f a -> f b
fmap2:: Functor g => (h -> h) -> g h -> g h
(+1) :: Num h => h -> h
Just :: i -> Maybe i
1 :: Num j => j
然后我们看到第一个fmap
(fmap1
)被调用,fmap (+1) :: Functor g => g h -> g h
作为第一个参数,Just :: i -> Maybe i
作为第二个参数。因此,如果我们进一步进行类型分析,我们会得到:(a -> b) ~ g h -> g h
,所以a ~ b ~ g h
,我们知道f (g h) ~ i -> Maybe i
,所以这意味着f (g h) ~ (->) i (Maybe i)
如此f ~ (->) i
和g ~ Maybe
,h ~ i
,进一步i ~ j
:
fmap1:: (Maybe i -> Maybe i) -> (->) i (Maybe i) -> (->) i Maybe i
fmap2:: (i -> i) -> Maybe i -> Maybe i
(+1) :: Num h => i -> i
Just :: i -> Maybe i
1 :: Num i => i
现在这里的一个关键方面是(->) r
也是一个函子,实际上在我们看到的base-4.10.1.0
源代码中:
instance Functor ((->) r) where fmap = (.)
我们在这里可以将函数视为函子,如果我们执行fmap
则"后处理"结果。所以这意味着在我们应用Just
之后,我们将fmap (+1)
应用于该结果。所以第一个fmap
等价于(.)
,而第二个是Maybe
的fmap
,因此我们得到:
((fmap (fmap (+1)) Just) 1
-> (((.) (fmap (+1)) Just) 1
-> ((x -> (fmap (+1) (Just x)) 1
-> fmap (+1) (Just 1)
-> Just 2
简而言之,我们将(fmap (+1))
用作后处理步骤,在我们将Just
应用于1
之后。