我正在尝试理解一些Haskell源代码,并且遇到了这种结构:
x <*> y <$> z
e.g.
(+) <*> (+1) <$> a
有人能向我解释这个结构吗?我知道它可以翻译成
fmap a (+ a + 1)
,但我无法建立联系。我正在尝试理解一些Haskell源代码,并且遇到了这种结构:
x <*> y <$> z
e.g.
(+) <*> (+1) <$> a
fmap a (+ a + 1)
,但我无法建立联系。让我们开始吧:
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
x <*> y <$> z
` 中,即 `fmap (x<*>y) z
`,你将函数 x<*>y
应用于函子值 z
。实际上,`<*>` 对 fmapping 一无所知 - 这两个运算符在完全不同的函子上工作!这是需要认识到的第一件重要的事情。<*>
` 的 `Applicative` 实例实际上是函数函子。我希望人们不要那么频繁地使用它,因为它实际上是更令人困惑的实例之一,通常不是最好的选择。(<*>) :: (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 ──┘
┌───id──┐
────&&& uncurry f ──▶
└─▶ g ──┘
(≻>>) :: (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
(它等价于liftA2 x y z
),而是x <*> y <$> z
,这更加令人困惑。 - duplodeliftA2
将成为一个 Applicative
方法,并且 Traversable
派生将在适当的情况下使用它。不幸的是,ApplicativeDo
的解糖是另一个非常棘手的问题,所以我还没有尝试将其整合进去。 - dfeuer
<*>
作为 S 组合子,利用它的(->) a
实例,可能会相当令人困惑。 - chiApplicative ((->) r)
有一些合法的用途(特别是,如果在上下文中f
和g
是函数这一事实非常明显或不相关,则op<$>f<*>g
对我来说并不麻烦)。无论如何,我们正在讨论的例子不是其中之一。(我同意chi的观点,使用独立的(<*>)
作为S组合器往往会令人困惑。) - duplode