简体   繁体   English

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

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

I am trying to understand how fmap fmap applies to a function like say (*3) . 我试图理解fmap fmap如何应用于像say (*3)这样的函数。

The type of fmap fmap : fmap fmap的类型:

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

Type of (*3) : 类型(*3)

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

Which means that the signature a -> a corresponds to f (a -> b) , right? 这意味着签名a -> a对应f (a -> b) ,对吧?

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

I have tried creating a simple test: 我尝试过创建一个简单的测试:

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

And feeding (*3) into it, but I get: 喂食(*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))

Why is that happening? 为什么会这样?

Polymorphism is dangerous when you do not know what you are doing. 当你不知道自己在做什么时,多态性很危险。 Both fmap and (*) are polymorphic functions and using them blindly can lead to very confusing (and possibly incorrect) code. fmap(*)都是多态函数,盲目地使用它们会导致非常混乱(也可能是错误的)代码。 I have answered a similar question before: 我之前回答过类似的问题:

What is happening when I compose * with + in Haskell? 当我在Haskell中用*编写时会发生什么?

In such cases, I believe that looking at the types of values can help you figure out where you are going wrong and how to rectify the problem. 在这种情况下,我认为查看值的类型可以帮助您找出出错的地方以及如何纠正问题。 Let's start with the type signature of fmap : 让我们从fmap的类型签名fmap

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

The type signature of fmap is easy to understand. fmap的类型签名很容易理解。 It lifts a function from a to b into the context of a functor, whatever that functor may be (eg list, maybe, either, etc.). 它升降机的函数从ab为算符,不管它仿函数可以是的上下文(例如列表,也许,要么,等)。

The words "domain" and "codomain" just mean "input" and "output" respectively. 单词“domain”和“codomain”分别仅表示“输入”和“输出”。 Anyway, let's see what happens when we apply fmap to fmap : 无论如何,让我们看到,当我们申请会发生什么fmapfmap

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

As you can see, a := x -> y and b := gx -> gy . 如您所见, a := x -> yb := gx -> gy In addition, the Functor g constraint is added. 此外,还添加了Functor g约束。 This gives us the type signature of fmap fmap : 这给了我们fmap fmap的类型签名:

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

So, what does fmap fmap do? 那么, fmap fmap做了什么? The first fmap has lifted the second fmap into the context of a functor f . 第一个fmap将第二个fmap提升到了一个fmap函数f的上下文中。 Let's say that f is Maybe . 让我们说fMaybe Hence, on specializing: 因此,在专业化:

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

Hence fmap fmap must be applied to a Maybe value with a function inside it. 因此,必须将fmap fmap应用于其中包含函数的Maybe值。 What fmap fmap does is that it lifts the function inside the Maybe value into the context of another functor g . fmap fmap作用是将Maybe值中的函数提升到另一个函子g的上下文中。 Let's say that g is [] . 假设g[] Hence, on specializing: 因此,在专业化:

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

If we apply fmap fmap to Nothing then we get Nothing . 如果我们将fmap fmap应用于Nothing那么我们得到Nothing However, if we apply it to Just (+1) then we get a function that increments every number of a list, wrapped in a Just constructor (ie we get Just (fmap (+1)) ). 但是,如果我们将它应用于Just (+1)那么我们得到一个函数,它增加列表的每个数字,包含在Just构造函数中(即我们得到Just (fmap (+1)) )。

However, fmap fmap is more general. 但是, fmap fmap更为通用。 What it actually does it that it looks inside a functor f (whatever f may be) and lifts the function(s) inside f into the context of another functor g . 它实际上做它,它看起来算符内f (无论f而定)和电梯里的功能(S) f进入另一个函子上下文g

So far so good. 到现在为止还挺好。 So what's the problem? 所以有什么问题? The problem is when you apply fmap fmap to (*3) . 问题是当您将fmap fmap应用于(*3) This is stupid and dangerous, like drinking and driving. 这是愚蠢和危险的,如酒后驾车。 Let me tell you why it's stupid and dangerous. 让我告诉你为什么它是愚蠢和危险的。 Take a look at the type signature of (*3) : 看一下(*3)的类型签名:

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

When you apply fmap fmap to (*3) then the functor f is specialized to (->) r (ie a function). 当您将fmap fmap应用于(*3)时, fmap fmap函数f专门用于(->) r (即函数)。 A function is a valid functor. 函数是一个有效的函子。 The fmap function for (->) r is simply function composition. (->) rfmap函数只是函数组合。 Hence, the type of fmap fmap on specializing is: 因此,专业化的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

Do you see why it's stupid and dangerous? 你明白为什么这是愚蠢和危险的吗?

  1. It's stupid because you are applying a function which expects an input function with two arguments ( r -> x -> y ) to a function with only one argument, (*3) :: Num a => a -> a . 这是愚蠢的,因为你正在应用一个函数,它需要一个带有两个参数( r -> x -> y )的输入函数到一个只有一个参数的函数, (*3) :: Num a => a -> a
  2. It's dangerous because the output of (*3) is polymorphic. 它很危险,因为(*3)的输出是多态的。 Hence, the compiler doesn't tell you that you are doing something stupid. 因此,编译器不会告诉您您正在做一些愚蠢的事情。 Luckily, because the output is bounded you get a type constraint Num (x -> y) which should indicate that you have gone wrong somewhere. 幸运的是,因为输出是有界的,你得到一个类型约束Num (x -> y) ,它应该表明你在某个地方出了问题。

Working out the types, r := a := x -> y . 计算出类型, r := a := x -> y Hence, we get the following type signature: 因此,我们得到以下类型签名:

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

Let me show you why it's wrong using values: 让我告诉你使用值时出错的原因:

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

What you really want to do is apply fmap fmap to (*) , which is a binary function: 你真正想要做的是将fmap fmap应用于(*) ,这是一个二元函数:

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

Hence, r := x := y := a . 因此, r := x := y := a This gives you the type signature: 这为您提供了类型签名:

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

This makes even more sense when you see the values: 当您看到值时,这更有意义:

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

Hence, fmap fmap (*) 3 is simply fmap (3*) . 因此, fmap fmap (*) 3只是fmap (3*)

Finally, you have the same problem with your test function: 最后,您的test功能存在同样的问题:

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

On specializing functor f to (->) r we get: 在专业化函子f(->) r我们得到:

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

Hence, r := x := a -> b . 因此, r := x := a -> b Thus we get type signature: 因此我们获得了类型签名:

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

Since neither a nor b appear in the output type, the constraint Num (a -> b) must be resolved immediately. 由于输出类型中既不出现a也不出现b ,因此必须立即解析约束Num (a -> b) If a or b appeared in the output type then they could be specialized and a different instance of Num (a -> b) could be chosen. 如果ab出现在输出类型中,则它们可以是专用的,并且可以选择不同的Num (a -> b)实例。 However, because they don't appear in the output type the compiler has to decide which instance of Num (a -> b) to choose immediately; 但是,因为它们没有出现在输出类型中,所以编译器必须决定立即选择哪个Num (a -> b)实例; and because Num (a -> b) is a stupid constraint which doesn't have any instance, the compiler throws an error. 并且因为Num (a -> b)是一个没有任何实例的愚蠢约束,编译器会抛出错误。

If you try test (*) then you won't get any error, for the same reason that I mentioned above. 如果您尝试test (*)那么您将不会收到任何错误,原因与上面提到的相同。

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

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