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
只是组合函数 f
和 g
的一种巧妙方式,同时还将初始输入直接传递给 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
我试图理解一些 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
只是组合函数 f
和 g
的一种巧妙方式,同时还将初始输入直接传递给 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