应用仿函数:为什么fmap可以使用多个参数的函数?

时间:2014-01-27 08:31:15

标签: haskell functor applicative

我进入Haskell并发现这本书“让你学习一个Haskell”最有帮助。我在applicative functors的部分。

我对书中出现的以下内容感到困惑:

(\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5

产生输出:

[8.0,10.0,2.5]

首先,我已经确认了我对ghci关于运算符优先级的怀疑,所以上面等于以下丑陋的陈述:

(((\x y z -> [x,y,z]) <$> (+3)) <*> (*2) <*> (/2)) $ 5 

因此,很明显,首先发生的事情是fmap通过(<$>)中缀运算符调用。

这是目前困扰我的思想的核心。 fmap(此处显示为中缀(<$>))的定义为:

(<$>) :: (Functor f) => (a -> b) -> f a -> f b

但是在我努力的等式中,(\x y z -> [x, y, z])需要三个参数,而不仅仅是一个。那么如何才能满足(a -> b)类型的第一个参数?

我认为它可能与部分应用/ currying有关,但我无法弄明白。我非常感谢你的解释。希望我已经很好地提出了这个问题。

5 个答案:

答案 0 :(得分:11)

简单回答: Haskell中没有多个参数的函数!

你可以称之为“二元函数”的函数有两个:一个函数需要一个(单个!)元组,而且 - 在Haskell中普遍存在 - curried函数。那些只需要一个参数,但结果又是一个函数。

那么,弄清楚例如fmap (+),我们写一下

type IntF = Int -> Int

-- (+) :: Int -> IntF
-- fmap :: ( a -> b  ) ->  f a -> f b
--  e.g.:: (Int->IntF) -> f Int->f IntF

在GHCi中自行测试:

  

前奏&GT; type IntF = Int - &gt;诠释
  前奏&GT; let(#)=(+):: Int - &gt; INTF
  前奏&GT; :t fmap(#)
  fmap(#):: Functor f =&gt; f Int - &gt; f IntF

答案 1 :(得分:7)

考虑类型

的函数
f :: a -> b -> c -> d

其中d是任何其他类型。由于currying,这可以被认为是具有以下类型的函数

f :: a -> (b -> c -> d)

即。采用a并返回类型b -> c -> d的函数的函数。如果您申请fmap,则

-- the type of fmap, which is also :: (a -> r) -> (f a -> f r)
fmap :: Functor f => (a -> r) -> f a -> f r

-- the type of f
f :: a -> (b -> c -> d)

-- so, setting r = b -> c -> d
fmap f :: f a -> f (b -> c -> d)

现在哪个类型被用作(<*>)的左手参数。

答案 2 :(得分:3)

因为你可以使用3参数函数,只给它一个参数,这会产生一个2参数函数。所以你最终会得到一个2参数函数列表。然后,您可以再应用一个参数,最后添加一个1参数函数列表,最后应用最后一个参数,最后得到一个普通数字列表。

顺便说一句,这是为什么 Haskell已经完成了功能。它可以很容易地编写像这样适用于任意数量的函数参数的结构。 : - )

答案 3 :(得分:2)

我个人觉得函数的applicative functor实例有点奇怪。我将引导您完成此示例,以便直观地了解正在发生的事情:

>>> :t (\x y z -> [x, y, z]) <$> (+3)
... :: Num a => a -> a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3)) 1 2 3
[4,2,3]

这将(+3)应用于内部函数的第一个参数。其他2个外部参数未经修改地传递给内部函数。

让我们添加一个应用程序:

>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2)
... :: Num a => a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3) <*> (*2)) 1 2
[4,2,2]

这会像以前一样将(+3)应用于第一个参数。使用applicative,第一个外部参数(1)被应用(*2)并作为内部函数的第二个参数传递。第二个外部参数未经修改地传递给内部函数作为其第三个参数。

猜猜当我们使用另一个应用程序时会发生什么:

>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2)
... :: Fractional a => a -> [a]
>>> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 1
[4.0,2.0,0.5]

3个相同参数的应用程序作为3个参数传递给内部函数。

这在理论上并不是可靠的解释,但它可以直观地说明函数的应用实例是如何工作的。

答案 4 :(得分:0)

背景

让我们从<*>pure的定义开始,以功能作为Applicative的实例。对于pure,它将采用任何垃圾值,并返回x。对于<*>,您可以认为它是将x应用于f,从中获取新功能,然后将其应用于g x的输出。

instance Applicative ((->) r) where  
    pure x = (\_ -> x)  
    f <*> g = \x -> f x (g x) 

现在,让我们看一下<$>的定义。它只是fmap的中缀版本。

(<$>) :: (Functor f) => (a -> b) -> f a -> f b  
f <$> x = fmap f x  

回想一下fmap具有以下实现:

instance Functor ((->) r) where  
    fmap f g = (\x -> f (g x))  

证明f <$> x只是pure f <*> x

让我们从pure f <*> x开始。将pure f替换为(\_ -> f)

pure f <*> x 
= (\_ -> f) <*> x

现在,让我们应用<*>的定义,即f <*> g = \q -> f q (g q)

(\_ -> f) <*> x
= \q -> (\_ -> f) q (x q)

请注意,我们可以将(\_ -> f) q简化为f。该函数接受我们提供的任何值,然后返回f

\q -> (\_ -> f) q (x q)
= \q -> f (x q)

这就像我们对fmap的定义一样! <$>运算符只是中缀fmap

\q -> f (x q)
= fmap f x
= f <$> x

请记住这一点:f <$> g只是pure f <*> g

了解(\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5

第一步是重写表达式的左侧以使用<*>而不是<$>。使用我们在上一节中证明的内容:

(\x y z -> [x, y, z]) <$> (+3)
= pure (\x y z -> [x, y, z]) <*> (+3)

因此完整的表达式变为

pure (\x y z -> [x, y, z]) <*> (+3) <*> (*2) <*> (/2) $ 5

使用<*>

的定义简化第一个运算符
pure (\x y z -> [x, y, z]) <*> (+3)
= \a -> f a (g a) --substitute f and g
= \a -> pure (\x y z -> [x, y, z]) a ((+3) a)

现在让我们用pure x代替(\_ -> x)。观察到a变成了用作_的垃圾值,并被消耗以返回函数(\x y z -> [x, y, z])

\a -> (\_-> (\x y z -> [x, y, z])) a ((+3) a)
= \a -> (\x y z -> [x, y, z]) ((+3) a)

现在让我们回顾一下完整的表达式,然后处理下一个<*>。同样,让我们​​应用<*>的定义。

(\a -> (\x y z -> [x, y, z]) ((+3) a)) <*> (*2)
= \b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)

最后,让我们最后一次重复此操作,以完成最后的<*>

(\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) <*> (/2)
= \c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)

请注意,它是一个具有单个值的函数。我们将其喂入5

(\c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)) 5
(\5 -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 ((/2) 5))
       (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 (2.5   )
       (\5 -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 ((*2) 5))   (2.5   )
              (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 (10    )    (2.5   )
              (\5 -> (\x y z -> [x, y, z]) ((+3) 5))   (10    )    (2.5   )           
                     (\x y z -> [x, y, z]) (8     )    (10    )    (2.5   )         

(\x y z -> [x, y, z]) (8) (10) (2.5)                     
= [8, 10, 2.5]

这就是我们得到最终答案的方式。