繁体   English   中英

fmap fmap如何应用于函数(作为参数)?

[英]How does fmap fmap apply to functions (as arguments)?

我试图理解fmap fmap如何应用于像say (*3)这样的函数。

fmap fmap的类型:

(fmap fmap):: (Functor f1, Functor f) => f (a -> b) -> f (f1 a -> f1 b)

类型(*3)

(*3) :: Num a => a -> a

这意味着签名a -> a对应f (a -> b) ,对吧?

Prelude> :t (fmap fmap (*3))
(fmap fmap (*3)):: (Num (a -> b), Functor f) => (a -> b) -> f a -> f b

我尝试过创建一个简单的测试:

test :: (Functor f) => f (a -> b) -> Bool 
test f = True

喂食(*3) ,但我得到:

*Main> :t (test (*3))

<interactive>:1:8:
    No instance for (Num (a0 -> b0)) arising from a use of ‘*’
    In the first argument of ‘test’, namely ‘(* 3)’
    In the expression: (test (* 3))

为什么会这样?

当你不知道自己在做什么时,多态性很危险。 fmap(*)都是多态函数,盲目地使用它们会导致非常混乱(也可能是错误的)代码。 我之前回答过类似的问题:

当我在Haskell中用*编写时会发生什么?

在这种情况下,我认为查看值的类型可以帮助您找出出错的地方以及如何纠正问题。 让我们从fmap的类型签名fmap

fmap :: Functor f => (a -> b) -> f a -> f b
                     |______|    |________|
                         |            |
                      domain      codomain

fmap的类型签名很容易理解。 它升降机的函数从ab为算符,不管它仿函数可以是的上下文(例如列表,也许,要么,等)。

单词“domain”和“codomain”分别仅表示“输入”和“输出”。 无论如何,让我们看到,当我们申请会发生什么fmapfmap

fmap :: Functor f => (a -> b) -> f a -> f b
                     |______|
                         |
                       fmap :: Functor g => (x -> y) -> g x -> g y
                                            |______|    |________|
                                                |            |
                                                a    ->      b

如您所见, a := x -> yb := gx -> gy 此外,还添加了Functor g约束。 这给了我们fmap fmap的类型签名:

fmap fmap :: (Functor f, Functor g) => f (x -> y) -> f (g x -> g y)

那么, fmap fmap做了什么? 第一个fmap将第二个fmap提升到了一个fmap函数f的上下文中。 让我们说fMaybe 因此,在专业化:

fmap fmap :: Functor g => Maybe (x -> y) -> Maybe (g x -> g y)

因此,必须将fmap fmap应用于其中包含函数的Maybe值。 fmap fmap作用是将Maybe值中的函数提升到另一个函子g的上下文中。 假设g[] 因此,在专业化:

fmap fmap :: Maybe (x -> y) -> Maybe ([x] -> [y])

如果我们将fmap fmap应用于Nothing那么我们得到Nothing 但是,如果我们将它应用于Just (+1)那么我们得到一个函数,它增加列表的每个数字,包含在Just构造函数中(即我们得到Just (fmap (+1)) )。

但是, fmap fmap更为通用。 它实际上做它,它看起来算符内f (无论f而定)和电梯里的功能(S) f进入另一个函子上下文g

到现在为止还挺好。 所以有什么问题? 问题是当您将fmap fmap应用于(*3) 这是愚蠢和危险的,如酒后驾车。 让我告诉你为什么它是愚蠢和危险的。 看一下(*3)的类型签名:

(*3) :: Num a => a -> a

当您将fmap fmap应用于(*3)时, fmap fmap函数f专门用于(->) r (即函数)。 函数是一个有效的函子。 (->) rfmap函数只是函数组合。 因此,专业化的fmap fmap类型是:

fmap fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y

-- or

(.)  fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y
                          |___________|
                                |
                              (*3) :: Num a => a ->    a
                                               |       |
                                               |    ------
                                               |    |    |
                                               r -> x -> y

你明白为什么这是愚蠢和危险的吗?

  1. 这是愚蠢的,因为你正在应用一个函数,它需要一个带有两个参数( r -> x -> y )的输入函数到一个只有一个参数的函数, (*3) :: Num a => a -> a
  2. 它很危险,因为(*3)的输出是多态的。 因此,编译器不会告诉您您正在做一些愚蠢的事情。 幸运的是,因为输出是有界的,你得到一个类型约束Num (x -> y) ,它应该表明你在某个地方出了问题。

计算出类型, r := a := x -> y 因此,我们得到以下类型签名:

fmap . (*3) :: (Num (x -> y), Functor g) => (x -> y) -> g x -> g y

让我告诉你使用值时出错的原因:

  fmap . (*3)
= \x -> fmap (x * 3)
             |_____|
                |
                +--> You are trying to lift a number into the context of a functor!

你真正想要做的是将fmap fmap应用于(*) ,这是一个二元函数:

(.) fmap :: Functor g => (r -> x -> y) -> r -> g x -> g y
                         |___________|
                               |
                              (*) :: Num a => a -> a -> a
                                              |    |    |
                                              r -> x -> y

因此, r := x := y := a 这为您提供了类型签名:

fmap . (*) :: (Num a, Functor g) => a -> g a -> g a

当您看到值时,这更有意义:

  fmap . (*)
= \x -> fmap (x *)

因此, fmap fmap (*) 3只是fmap (3*)

最后,您的test功能存在同样的问题:

test :: Functor f => f (a -> b) -> Bool

在专业化函子f(->) r我们得到:

test :: (r -> a -> b) -> Bool
        |___________|
              |
            (*3) :: Num x => x ->    x
                             |       |
                             |    ------
                             |    |    |
                             r -> a -> b

因此, r := x := a -> b 因此我们获得了类型签名:

test (*3) :: Num (a -> b) => Bool

由于输出类型中既不出现a也不出现b ,因此必须立即解析约束Num (a -> b) 如果ab出现在输出类型中,则它们可以是专用的,并且可以选择不同的Num (a -> b)实例。 但是,因为它们没有出现在输出类型中,所以编译器必须决定立即选择哪个Num (a -> b)实例; 并且因为Num (a -> b)是一个没有任何实例的愚蠢约束,编译器会抛出错误。

如果您尝试test (*)那么您将不会收到任何错误,原因与上面提到的相同。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM