fmap 如何与数据构造函数一起使用?



我正在尝试理解一些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解析为函子(->) af1解析为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是表达式中的第ifmap(如果我们从左到右阅读)。如果我们知道执行一些分析,我们会发现,既然我们使用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 ~ (->) ig ~ Maybeh ~ 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等价于(.),而第二个是Maybefmap,因此我们得到:

((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之后

最新更新