【问题标题】:How to use Applicative for concurrency?如何使用 Applicative 进行并发?
【发布时间】:2015-01-01 12:14:33
【问题描述】:

这是我之前的question 的后续。我从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 建议使用新的 monad Fetch 使 API 可组合。

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

现在我可以用 monadic 组合定义我的getPageData: Fetch[A]

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

但它不会同时运行 getRecentgetPopulargetTopics

Haxl 建议使用 applicative 组合 &lt;*&gt; 组合“并发”函数(即可以同时运行的函数)。所以我的问题是:

  • 假设Fetch[A]Applicative,如何实现getPageData
  • 如何将Fetch 实现为Applicative 而不是Monad

【问题讨论】:

    标签: scala concurrency functional-programming applicative haxl


    【解决方案1】:

    假设 Fetch[A] 是一个 Applicative ,如何实现 getPageData ?

    我们需要做的就是放弃一元绑定&gt;&gt;=,转而使用应用&lt;*&gt;。所以不是

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

    我们会写一些类似的东西(用 Haskell 语法;抱歉,我不能在头脑中使用 Scala):

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

    但这是否达到预期的效果取决于第二个问题!

    如何将 Fetch 实现为 Applicative 而不是 Monad?

    monadic 和 applicative 排序之间的主要区别在于,monadic 可以依赖于 monadic 值中的值,而 applicative &lt;*&gt; 不能。请注意上面getPageData 的一元表达式如何在到达getTopics 之前绑定名称recentpopular。这些名称​​可以用于更改表达式的结构,例如在recent 为空的情况下获取其他数据源。但是对于应用表达式,getRecentgetPopular 的结果不是表达式本身结构的因素。这个属性允许我们同时触发应用表达式中的每个项,因为我们静态地知道表达式的结构。

    因此,使用上面的观察结果,显然是 Fetch 数据类型的特殊形状,我们可以为&lt;*&gt; 提出一个合适的定义。我认为以下内容说明了总体思路:

    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
    

    为了比较,假设我们尝试使用并发评估进行单子绑定:

    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
    

    【讨论】:

    • 非常感谢。我不知道Haskell 这就是我要问的原因...您是否将Fetch[A] 定义为具有IO[A] 类型的字段runFetch 的记录?
    • 没错,Fetch[A] 有一条 runFetch 记录,即 IO[A]。所以 Fetch[A] 实际上只是 IO[A] 的一个新名称,允许我们给出新的 &gt;&gt;=&lt;*&gt; 定义。
    • 再次感谢。我想我明白了。 BTW HaxlFetch 的定义不同,我会尝试再读一遍,可能会在这里提出更多问题。
    • 没问题!是的,我对Fetch 的定义只是为了说明在monad 中不可能实现并发的应用程序背后的一般思想。我相信 Haxl 的 fetch 必然更复杂,因为它处理缓存。
    • Haxl 的Fetch 只是一棵树; &lt;*&gt; 只是添加一个新节点。它们还有一个单独的 fetch 函数,它解释 Fetch 树并实际获取数据。 fetch 处理所有这些缓存、批处理、异步。等
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-16
    • 2014-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多