6

这是我之前的问题的后续。我从Haxl复制了下面的示例

假设我正在从博客服务器获取数据以呈现博客页面,该页面包含最近的帖子、热门帖子和帖子主题。

我有以下数据获取 API:

val getRecent  : Server => Seq[Post] = ...
val getPopular : Server => Seq[Post] = ...
val getTopics  : Server => Seq[Topic] = ...

现在我需要组合它们来实现一个新功能getPageData

val getPageData: Server => (Seq[Post],  Seq[Post], Seq[Topic])

Haxl建议使用新的 monadFetch来使 API 可组合。

val getRecent  : Fetch[Seq[Posts]] = ...
val getPopular : Fetch[Seq[Posts]] = ...
val getTopics  : Fetch[Seq[Topic]] = ...

现在我可以getPageData: Fetch[A]一元组合来定义我的

val getPageData = for {
  recent  <- getRecent
  popular <- getPopular
  topics  <- getTopics
} yield (recent, popular, topics)

但它不会同时运行getRecentgetPopulargetTopics

Haxl建议使用应用组合<*>来组合“并发”函数(即可以同时运行的函数)。所以我的问题是:

  • 如何实现getPageData假设Fetch[A]是一个Applicative
  • 如何实现Fetch为一个Applicative而不是一个Monad
4

1 回答 1

4

How to implement getPageData assuming Fetch[A] is an Applicative ?

All we need to do is drop the monadic bind >>= in favour of the applicative <*>. So instead of

val getPageData = for {
  recent  <- getRecent
  popular <- getPopular
  topics  <- getTopics
} yield (recent, popular, topics)

we would write something like (in Haskell syntax; sorry, I can't do Scala off the top of my head):

getPageData = makeTriple <$> getRecent <*> getPopular <*> getTopics
  where
    makeTriple x y z = (x, y, z)

But whether this has the desired effect is contingent upon the second question!

How to implement Fetch as an Applicative but not a Monad ?

The key distinction between monadic and applicative sequencing is that the monadic one can depend upon the value inside a monadic value, whereas the applicative <*> cannot. Notice how the monadic expression for getPageData above binds the names recent and popular before reaching getTopics. Those names could have been used to change the structure of the expression, for example by getting some other data source in case recent is empty. But with the applicative expression, the results of getRecent and getPopular are not factors in the structure of the expression itself. This property allows us to fire off each term in the applicative expression concurrently, because we know the structure of the expression statically.

So, using the observation above, and obviously the particular shape of the Fetch datatype, we can come up with a suitable definition for <*>. I think the following illustrates the general idea:

data Fetch a = Fetch { runFetch :: IO a }

fetchF <*> fetchX = Fetch $ do
  -- Fire off both IOs concurrently.
  resultF <- async $ runFetch fetchF
  resultX <- async $ runFetch fetchX
  -- Wait for both results to be ready.
  f <- wait resultF
  x <- wait resultX
  return $ f x

For comparison, suppose we tried to do monadic bind with concurrent evaluation:

fetchF >>= fetchK = Fetch $ do
  resultF <- async $ runFetch fetchF
  -- Oh no, we need resultF in order to produce the next
  -- Fetch value! We just have to wait...
  f <- wait resultF
  fetchX <- async $ runFetch (fetchK f)
  x <- wait $ runFetch fetchX
  return $ f x
于 2015-01-02T05:15:16.460 回答