繁体   English   中英

应用函子:为什么 fmap 可以接受一个有多个参数的函数?

[英]Applicative functors: why can fmap take a function with more than one argument?

我正在进入 Haskell 并发现“Learn you a 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

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

我认为这可能与部分应用/柯里化有关,但我无法弄清楚。 我将不胜感激的解释。 希望我已经把问题表述得足够好。

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

您可能会称之为“二元函数”有两个候选函数:一个接受(单个!)元组的函数,以及——到目前为止在 Haskell 中流行的——柯里化函数 那些只需要一个参数,但结果又是一个函数。

所以,为了弄清楚fmap (+)作用,让我们写

type IntF = Int -> Int

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

在 GHCi 中自行测试:

前奏> 输入 IntF = Int -> Int
前奏> let (#) = (+) :: Int -> IntF
前奏> :t fmap (#)
fmap (#) :: Functor f => f Int -> f IntF

考虑一个类型的函数

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

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

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)

现在是用作(<*>)的左侧参数的正确类型。

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

顺便说一句,这就是Haskell 具有柯里化函数的原因。 编写这样的结构很容易,它适用于任意数量的函数参数。 :-)

我个人觉得函数的 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)并作为内部函数的第二个参数传递。 第二个外部参数未经修改地作为第三个参数传递给内部函数。

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

>>> :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 个参数传递给内部函数。

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

背景

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

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 (\\_ -> f)替换pure f

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

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

(\_ -> 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

理解(\\xyz -> [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)

现在让我们用(\\_ -> x)替换pure x 观察到a成为用作_的垃圾值,并被消耗以返回函数(\\xyz -> [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]

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

暂无
暂无

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

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