繁体   English   中英

(r - >)applicative functor

[英](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)

你有一个从rab的函数和一个从ra的函数。 你想要一个从rb的功能。 你知道的第一件事是你返回一个函数:

\x ->

现在你想要应用f因为它是唯一可以返回b

\x -> f _ _

现在f的参数是ra的类型。 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

请注意,它们非常相似! 实际上, <*>运算符有效地推广了应用程序,使其可以根据所涉及的类型进行过载 使用最简单的ApplicativeIdentity时很容易看到:

ghci> Identity (+) <*> Identity 1 <*> Identity 2
Identity 3

这也可以通过稍微复杂的应用函数来看到,例如Maybe

ghci> Just (+) <*> Just 1 <*> Just 2
Just 3
ghci> Just (+) <*> Nothing <*> Just 2
Nothing

对于(->) rApplicative实例执行一种函数组合,它生成一个新函数,它接受一种“上下文”并将其线程化为所有值以生成函数及其参数:

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 FunctorApplicative实例往往有点令人困惑。 (->) 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM