x <*> y <$> z Haskell

x <*> y <$> z in Haskell

我试图理解一些 Haskell 源代码,我有时遇到这种结构:

x <*> y <$> z

例如

(+) <*> (+1) <$> a

谁能给我解释一下这个结构?我知道它转换为 fmap a (+ a + 1),但我无法建立连接

因此在 x <*> y <$> z 中,即 fmap (x<*>y) z,您将函数 x<*>y 应用于函子值 z<*> 实际上对 fmapping 一无所知——这两个运算符在完全独立的函子上工作!在这里要意识到的第一件重要的事情。

接下来是,如果x<*>y的结果是一个函数,那么<*>Applicative实例实际上就是。我希望人们不要那么多地使用它,因为它确实是更令人困惑的实例之一,而且通常不是最好的抽象选择。

具体来说,f<*>g 只是组合函数 fg 的一种巧妙方式,同时还将初始输入直接传递给 f。它是这样工作的:

(<*>) :: (f ~ (x->))
     => f (a -> b) -> f a -> f b

(<*>) :: (x ->(a -> b)) -> (x -> a) -> (x -> b)
       ≡ (x -> a -> b)  -> (x -> a) ->  x -> b
(f <*> g) x = f x $ g x

从数据流的角度来说,就是这个操作:

────┬─────▶ f ──▶
    │       │
    └─▶ g ──┘

我宁愿用arrow combinators来表达:

     ┌───id──┐
────&&&     uncurry f ──▶
     └─▶ g ──┘

所以f<*>g ≡ id &&& g >>> uncurry f。诚然,这并不紧凑,实际上比显式 lambda 版本 \x -> f x $ g x 更冗长,坦率地说,这可能是最好的。然而,箭头版本是这三个版本中最通用的版本,可以说是最好地表达了正在发生的事情。它如此冗长的主要原因是柯里化在这里不起作用;我们可以定义一个运算符

(≻>>) :: (x->(a,b)) -> (a->b->c) -> x -> c
g≻>>h = uncurry h . g

然后

         x <*> y <$> z
≡ fmap (id &&& y ≻>> x) z
≡ fmap (\ξ -> x ξ $ y ξ) z

例如,我们有

   (+) <*> (+1) <$> a
≡ fmap (id &&& (+1) ≻>> (+)) z
≡ fmap (\x -> 1 + x+1) z
≡ fmap (+2) z

我首先看错了你的问题。模式 <$> <*> 比您的 <*> <$> 更常见,以下地址...也许对其他人有用。

f <$> y <*> z 也可以写成 liftA2 f y z,而 liftA2 在我看来比等价的 <*>.

更容易理解
liftA2 :: (a -> b -> c) -> f a -> f b -> f c

它所做的是,它采用值的组合器函数并从中产生容器上的组合器函数。它有点类似于 zipWith 除了列表实例,它不仅将 a 列表中的每个元素与 中对应的元素 组合在一起 b 列表,但将 a 列表中的每个元素与 b 列表中的所有元素 组合一次 ,然后连接结果。

Prelude> Control.Applicative.liftA2 (+) [0,10..30] [0..3]
[0,1,2,3,10,11,12,13,20,21,22,23,30,31,32,33]

让我们开始:

x <*> y <$> z

加上括号,变成:

(x <*> y) <$> z

鉴于 (<$>) :: Functor f => (a -> b) -> f a -> f b,我们有:

x <*> y :: a -> b
z :: Functor f => f a

鉴于 (<*>) :: Applicative g => g (c -> d) -> g c -> g d,我们有:

x :: Applicative g => g (c -> d)
y :: Applicative g => g c
x <*> y :: Applicative g => g d

结合最后几个结果,我们得到:

g d ~ a -> b
g ~ (->) a
d ~ b

x :: a -> c -> b
y :: a -> c
x <*> y :: a -> b

因此:

(\x y z -> x <*> y <$> z) :: Functor f => (a -> c -> b) -> (a -> c) -> f a -> f b

现在知道正在使用函数实例中的(<*>),我们也可以替换它的定义:

x <*> y <$> z
(\r -> x r (y r)) <$> z

在您的示例中,x = (+)y = (+1)z = a,所以我们得到...

(\r -> r + (r + 1)) <$> a

... 将 a 中的每个值加一:

GHCi> (+) <*> (+1) <$> [0..3]
[1,3,5,7]
GHCi> ((+) <*> (+1) <$> (*5)) 2
21