[英](r ->) applicative functor
我在理解Applicative的函数实例(->) r
如何在Haskell中工作时遇到了一些麻烦。
例如,如果我有
(+) <$> (+3) <*> (*100) $ 5
我知道你得到了结果508,我知道你得到(5 + 3)
和(5 * 100)
并且你将(+)
函数应用于这两个。
但是我不太明白发生了什么。 我假设表达式括号如下:
((+) <$> (+3)) <*> (*100)
根据我的理解,发生的事情是你在(+3)
的最终结果上的映射(+)
,然后你使用<*>
运算符将该函数应用于(*100)
的最终结果
但是我不理解(->) r
实例的<*>
的实现以及为什么我不能写:
(+3) <*> (*100)
<*>
, <$>
运算符在(->) r
时是如何工作的?
<$>
只是fmap
另一个名称, 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
的功能。 你知道的第一件事是你返回一个函数:
\x ->
现在你想要应用f
因为它是唯一可以返回b
:
\x -> f _ _
现在f
的参数是r
和a
的类型。 r
只是x
(因为它的alrady是r
类型,你可以通过将g
应用于x
得到a
:
\x -> f x (g x)
Aaand你已经完成了。 这是Haskell Prelude中实现的链接 。
考虑<*>
的类型签名:
(<*>) :: 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
函数,它具有将纯值“提升”为applicative functor的相同目的:
ghci> (pure (+) <*> (+ 3) <*> (* 100)) 5
508
但实际上,您很少会看到pure x <*> y
因为它与Applicative
定律完全等同于x <$> y
,因为<$>
只是fmap
的中缀同义词。 因此,我们有一个共同的习语:
ghci> ((+) <$> (+ 3) <*> (* 100)) 5
508
更一般地说,如果您看到任何表达式如下所示:
f <$> a <*> b
...除了在特定的Applicative
实例的习语的上下文中,您可以或多或少地像普通函数应用程序fab
一样阅读它。 实际上, 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))
让我们来看看这些函数的类型(以及我们自动与之相关的定义):
(<$>) :: (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
应用于x :: r
。
有趣的是, <*>
实际上是SKI组合微积分的S
函数,而(-> r)
pure
(-> r)
是K :: a -> b -> a
(常数)函数。
(->) e
Functor
和Applicative
实例往往有点令人困惑。 将(->) e
视为Reader e
的“脱衣服”版本可能会有所帮助。
newtype Reader e a = Reader
{ runReader :: e -> a }
名称e
应该用来表示“环境”这个词。 该类型Reader ea
应该被解读为“产生类型的值计算a
给定类型的环境e
”。
给定Reader ea
类型的计算,您可以修改其输出:
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
推理作为任何其他应用程序仿函数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.