为 Reader r 编写 Applicative 实例时如何编写 <*>

How to write the <*> when writing the Applicative instance for Reader r

我在 Haskell Book、"Chapter 22. Reader" 中坚持练习。练习说 "Implement the Applicative for Reader" 并给出以下内容:

{-# LANGUAGE InstanceSigs #-}

newtype Reader r a =
  Reader { runReader :: r -> a }

instance Applicative (Reader r) where 
  pure :: a -> Reader r a
  pure a = Reader $ ???

  (<*>) :: Reader r (a -> b) -> Reader r a -> Reader r b
  (Reader rab) <*> (Reader ra) = Reader $ \r -> ???

在写了一个 Functor 实例之后,我能够写出 pure(我写了 Functor 实例,否则 GHC 会抱怨 "No instance for (Functor (Reader r)) … arising from the superclasses of an instance declaration In the instance declaration for ‘Applicative (Reader r)’"):

{-# LANGUAGE InstanceSigs #-}

newtype Reader r a =
  Reader { runReader :: r -> a }

instance Functor (Reader r) where
  fmap f (Reader x) = Reader (f . x)

instance Applicative (Reader r) where
  pure :: a -> Reader r a
  pure a = Reader $ \_ -> a

  (<*>) :: Reader r (a -> b) -> Reader r a -> Reader r b
  (Reader rab) <*> (Reader ra) = Reader $ \r -> ???

但我坚持 ??? 部分。

书中给出了如下提示:

We got the definition of the apply function started for you, we’ll describe what you need to do and you write the code. If you unpack the type of Reader’s apply above, you get the following.

<*> :: (r -> a -> b) 
    -> (r -> a) 
    -> (r -> b)

 -- contrast this with the type of fmap

fmap :: (a -> b) 
     -> (r -> a)
     -> (r -> b)

So what’s the difference? The difference is that apply, unlike fmap, also takes an argument of type r.

Make it so.

是的,但如何做到这一点?使用 typed holes,编译器告诉我 ??? 的类型必须是 b。但我仍然看不出如何构造一个采用 r 和 returns 类型 b 的 lambda 表达式,给定 rabra

让我们打网球吧。看看你在范围内的作品,

rab :: r -> (a -> b)
ra :: r -> a
r :: r

b 的目标类型,您可以看到获得 b 的唯一方法是将 rab 应用于两个参数。

Reader rab <*> Reader ra = Reader $ \r -> rab _ _

现在,第一个洞的类型为 r,而您的范围内只有一个 r

Reader rab <*> Reader ra = Reader $ \r -> rab r _

剩下的洞的类型是a。您在范围内的唯一 ara

的 return 值
Reader rab <*> Reader ra = Reader $ \r -> rab r (ra _)

ra的论点必须是r,对此你只有一个选择。

Reader rab <*> Reader ra = Reader $ \r -> rab r (ra r)

请注意 rabra 都接收 r 作为参数。组合 Reader 计算中的所有步骤都可以访问相同的环境。

顺便说一下,这个定义使得 <*> 等同于著名的 S combinator(而 pureK)。