(r ->) 应用函子
(r ->) applicative functor
我无法理解 Applicative 的函数实例 (->) r
在 Haskell 中的工作原理。
例如,如果我有
(+) <$> (+3) <*> (*100) $ 5
我知道你得到了结果 508,我知道你得到了 (5 + 3)
和 (5 * 100)
的结果,然后将 (+)
函数应用于这两个。
不过我不是很明白这是怎么回事。我假设表达式用括号括起来如下:
((+) <$> (+3)) <*> (*100)
根据我的理解,正在发生的事情是您将 (+)
映射到 (+3)
的最终结果,然后您正在使用 <*>
运算符将该函数应用于最终结果(*100)
但是我不明白 (->) r
实例的 <*>
的实现以及为什么我不能写:
(+3) <*> (*100)
<*>
、<$>
运算符在涉及 (->) r
时如何工作?
<$>
只是 fmap
的另一个名称,它对 (->) r
的定义是 (.)
(组合运算符):
intance Functor ((->) r) where
fmap f g = f . g
你基本上可以通过查看类型来计算 <*>
的实现:
instance Applicative ((->) r) where
(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
f <*> g = \x -> f x (g x)
您有一个从 r
到 a
到 b
的函数和一个从 r
到 a
的函数。结果你想要一个从 r
到 b
的函数。你知道的第一件事是你 return 一个函数:
\x ->
现在您要应用 f
,因为它是唯一可以 return 成为 b
:
的项目
\x -> f _ _
现在 f
的参数是 r
和 a
类型。 r
只是 x
(因为它已经是 r
类型,您可以通过将 g
应用于 x
来获得 a
:
\x -> f x (g x)
啊啊,大功告成。 Here's a link to the implementation in Haskell's Prelude.
让我们来看看这些函数的类型(以及我们自动与它们相处的定义):
(<$>) :: (a -> b) -> (r -> a) -> r -> b
f <$> g = \x -> f (g x)
(<*>) :: (r -> a -> b) -> (r -> a) -> r -> b
f <*> g = \x -> f x (g x)
在第一种情况下,<$>
,实际上只是函数组合。一个更简单的定义是 (<$>) = (.)
.
第二种情况有点混乱。我们的第一个输入是一个函数f :: r -> a -> b
,我们需要得到一个b
类型的输出。我们可以提供 x :: r
作为 f
的第一个参数,但是我们可以将 a
作为第二个参数的类型的唯一方法是将 g :: r -> a
应用于 [=15] =].
有趣的是,<*>
实际上是 SKI combinatory calculus 的 S
函数,而 (-> r)
的 pure
是 K :: a -> b -> a
(常数)函数。
考虑 <*>
的类型签名:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
将其与普通函数应用程序的类型签名进行比较,$
:
($) :: (a -> b) -> a -> b
请注意,它们非常相似!事实上,<*>
运算符有效地泛化了应用程序,因此它可以根据所涉及的类型 重载 。这个用最简单的Applicative
,Identity
:
就很容易看出来
ghci> Identity (+) <*> Identity 1 <*> Identity 2
Identity 3
这也可以用稍微复杂一点的应用函子看到,比如Maybe
:
ghci> Just (+) <*> Just 1 <*> Just 2
Just 3
ghci> Just (+) <*> Nothing <*> Just 2
Nothing
对于 (->) r
,Applicative
实例执行一种函数组合,它生成一个新函数,该函数接受一种“上下文”并将其线程化到所有值以生成函数及其参数:
ghci> ((\_ -> (+)) <*> (+ 3) <*> (* 100)) 5
508
在上面的例子中,我只使用了<*>
,所以我明确写出第一个参数忽略它的参数并且总是产生(+)
。但是,Applicative
类型类还包括 pure
函数,它具有将纯值“提升”为应用函子的相同目的:
ghci> (pure (+) <*> (+ 3) <*> (* 100)) 5
508
但在实践中,您很少会看到 pure x <*> y
,因为根据 Applicative
法则它恰好等同于 x <$> y
,因为 <$>
只是一个中缀同义词fmap
。因此,我们有了常用的成语:
ghci> ((+) <$> (+ 3) <*> (* 100)) 5
508
更一般地说,如果您看到任何类似这样的表达式:
f <$> a <*> b
...你可以或多或少地像普通函数应用程序 f a b
一样阅读它,除了在特定 Applicative
实例的习语的上下文中。事实上,Applicative
的原始表述提出了“成语括号”的想法,即为上述表达式添加以下语法糖:
(| f a b |)
然而,Haskellers 似乎对中缀运算符非常满意,认为添加额外语法的好处不值得付出代价,因此 <$>
和 <*>
仍然是必要的。
作为我自己的 Haskell 新手,我会尽我所能解释最好的方法
<$>
运算符与将一个函数映射到另一个函数相同。
当你这样做时:
(+) <$> (+3)
你基本上是这样做的:
fmap (+) (+3)
以上将调用 (->) r 的 Functor 实现,如下所示:
fmap f g = (\x -> f (g x))
所以fmap (+) (+3)
的结果是(\x -> (+) (x + 3))
请注意,此表达式的结果类型为 a -> (a -> a)
这是一个应用程序!这就是为什么您可以将 (+) <$> (+3)
的结果传递给 <*>
运算符!
您可能会问为什么它是一个应用程序?让我们看看 <*>
定义:
f (a -> b) -> f a -> f b
注意第一个参数匹配我们返回的函数定义a -> (a -> a)
现在,如果我们看一下 <*>
运算符的实现,它看起来像这样:
f <*> g = (\x -> f x (g x))
所以当我们将所有这些部分放在一起时,我们得到:
(+) <$> (+3) <*> (+5)
(\x -> (+) (x + 3)) <*> (+5)
(\y -> (\x -> (+) (x + 3)) y (y + 5))
(\y -> (+) (y + 3) (y + 5))
(->) e
Functor
和 Applicative
实例往往有点令人困惑。将 (->) e
视为 Reader e
的 "undressed" 版本可能会有所帮助。
newtype Reader e a = Reader
{ runReader :: e -> a }
名字 e
应该暗示单词 "environment"。类型 Reader e a
应读作 "a computation that produces a value of type a
given an environment of type e
".
给定类型 Reader e a
的计算,您可以修改其输出:
instance Functor (Reader e) where
fmap f r = Reader $ \e -> f (runReader r e)
即先运行给定环境下的计算,再应用映射函数
instance Applicative (Reader e) where
-- Produce a value without using the environment
pure a = Reader $ \ _e -> a
-- Produce a function and a value using the same environment;
-- apply the function to the value
rf <*> rx = Reader $ \e -> (runReader rf e) (runReader rx e)
您可以像任何其他应用函子一样使用通常的 Applicative
推理。
我无法理解 Applicative 的函数实例 (->) r
在 Haskell 中的工作原理。
例如,如果我有
(+) <$> (+3) <*> (*100) $ 5
我知道你得到了结果 508,我知道你得到了 (5 + 3)
和 (5 * 100)
的结果,然后将 (+)
函数应用于这两个。
不过我不是很明白这是怎么回事。我假设表达式用括号括起来如下:
((+) <$> (+3)) <*> (*100)
根据我的理解,正在发生的事情是您将 (+)
映射到 (+3)
的最终结果,然后您正在使用 <*>
运算符将该函数应用于最终结果(*100)
但是我不明白 (->) r
实例的 <*>
的实现以及为什么我不能写:
(+3) <*> (*100)
<*>
、<$>
运算符在涉及 (->) r
时如何工作?
<$>
只是 fmap
的另一个名称,它对 (->) r
的定义是 (.)
(组合运算符):
intance Functor ((->) r) where
fmap f g = f . g
你基本上可以通过查看类型来计算 <*>
的实现:
instance Applicative ((->) r) where
(<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
f <*> g = \x -> f x (g x)
您有一个从 r
到 a
到 b
的函数和一个从 r
到 a
的函数。结果你想要一个从 r
到 b
的函数。你知道的第一件事是你 return 一个函数:
\x ->
现在您要应用 f
,因为它是唯一可以 return 成为 b
:
\x -> f _ _
现在 f
的参数是 r
和 a
类型。 r
只是 x
(因为它已经是 r
类型,您可以通过将 g
应用于 x
来获得 a
:
\x -> f x (g x)
啊啊,大功告成。 Here's a link to the implementation in Haskell's Prelude.
让我们来看看这些函数的类型(以及我们自动与它们相处的定义):
(<$>) :: (a -> b) -> (r -> a) -> r -> b
f <$> g = \x -> f (g x)
(<*>) :: (r -> a -> b) -> (r -> a) -> r -> b
f <*> g = \x -> f x (g x)
在第一种情况下,<$>
,实际上只是函数组合。一个更简单的定义是 (<$>) = (.)
.
第二种情况有点混乱。我们的第一个输入是一个函数f :: r -> a -> b
,我们需要得到一个b
类型的输出。我们可以提供 x :: r
作为 f
的第一个参数,但是我们可以将 a
作为第二个参数的类型的唯一方法是将 g :: r -> a
应用于 [=15] =].
有趣的是,<*>
实际上是 SKI combinatory calculus 的 S
函数,而 (-> r)
的 pure
是 K :: a -> b -> a
(常数)函数。
考虑 <*>
的类型签名:
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
将其与普通函数应用程序的类型签名进行比较,$
:
($) :: (a -> b) -> a -> b
请注意,它们非常相似!事实上,<*>
运算符有效地泛化了应用程序,因此它可以根据所涉及的类型 重载 。这个用最简单的Applicative
,Identity
:
ghci> Identity (+) <*> Identity 1 <*> Identity 2
Identity 3
这也可以用稍微复杂一点的应用函子看到,比如Maybe
:
ghci> Just (+) <*> Just 1 <*> Just 2
Just 3
ghci> Just (+) <*> Nothing <*> Just 2
Nothing
对于 (->) r
,Applicative
实例执行一种函数组合,它生成一个新函数,该函数接受一种“上下文”并将其线程化到所有值以生成函数及其参数:
ghci> ((\_ -> (+)) <*> (+ 3) <*> (* 100)) 5
508
在上面的例子中,我只使用了<*>
,所以我明确写出第一个参数忽略它的参数并且总是产生(+)
。但是,Applicative
类型类还包括 pure
函数,它具有将纯值“提升”为应用函子的相同目的:
ghci> (pure (+) <*> (+ 3) <*> (* 100)) 5
508
但在实践中,您很少会看到 pure x <*> y
,因为根据 Applicative
法则它恰好等同于 x <$> y
,因为 <$>
只是一个中缀同义词fmap
。因此,我们有了常用的成语:
ghci> ((+) <$> (+ 3) <*> (* 100)) 5
508
更一般地说,如果您看到任何类似这样的表达式:
f <$> a <*> b
...你可以或多或少地像普通函数应用程序 f a b
一样阅读它,除了在特定 Applicative
实例的习语的上下文中。事实上,Applicative
的原始表述提出了“成语括号”的想法,即为上述表达式添加以下语法糖:
(| f a b |)
然而,Haskellers 似乎对中缀运算符非常满意,认为添加额外语法的好处不值得付出代价,因此 <$>
和 <*>
仍然是必要的。
作为我自己的 Haskell 新手,我会尽我所能解释最好的方法
<$>
运算符与将一个函数映射到另一个函数相同。
当你这样做时:
(+) <$> (+3)
你基本上是这样做的:
fmap (+) (+3)
以上将调用 (->) r 的 Functor 实现,如下所示:
fmap f g = (\x -> f (g x))
所以fmap (+) (+3)
的结果是(\x -> (+) (x + 3))
请注意,此表达式的结果类型为 a -> (a -> a)
这是一个应用程序!这就是为什么您可以将 (+) <$> (+3)
的结果传递给 <*>
运算符!
您可能会问为什么它是一个应用程序?让我们看看 <*>
定义:
f (a -> b) -> f a -> f b
注意第一个参数匹配我们返回的函数定义a -> (a -> a)
现在,如果我们看一下 <*>
运算符的实现,它看起来像这样:
f <*> g = (\x -> f x (g x))
所以当我们将所有这些部分放在一起时,我们得到:
(+) <$> (+3) <*> (+5)
(\x -> (+) (x + 3)) <*> (+5)
(\y -> (\x -> (+) (x + 3)) y (y + 5))
(\y -> (+) (y + 3) (y + 5))
(->) e
Functor
和 Applicative
实例往往有点令人困惑。将 (->) e
视为 Reader e
的 "undressed" 版本可能会有所帮助。
newtype Reader e a = Reader
{ runReader :: e -> a }
名字 e
应该暗示单词 "environment"。类型 Reader e a
应读作 "a computation that produces a value of type a
given an environment of type e
".
给定类型 Reader e a
的计算,您可以修改其输出:
instance Functor (Reader e) where
fmap f r = Reader $ \e -> f (runReader r e)
即先运行给定环境下的计算,再应用映射函数
instance Applicative (Reader e) where
-- Produce a value without using the environment
pure a = Reader $ \ _e -> a
-- Produce a function and a value using the same environment;
-- apply the function to the value
rf <*> rx = Reader $ \e -> (runReader rf e) (runReader rx e)
您可以像任何其他应用函子一样使用通常的 Applicative
推理。