Scala中的功能并行和懒惰

时间:2017-11-25 16:14:55

标签: scala concurrency parallel-processing functional-programming lazy-evaluation

背景

我一直在阅读这本书Functional Programming in Scala,并对Chapter 7: Purely functional parallelism中的内容有一些疑问。

以下是本书中答案的代码:Par.scala,但我对其某些部分感到困惑。

以下是FirebaseAuth.getInstance().addAuthStateListener(new AuthStateListener() { public void onAuthStateChanged(FirebaseAuth auth) { FirebaseUser user = firebaseAuth.getCurrentUser(); if (user != null) { clientsCollection = db.collection(FIRESTORE_COLLECTION_USERS) .document(user.getUid()) .collection(FIRESTORE_COLLECTION_CLIENTS); clientsCollection .get() .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() { @Override public void onComplete(@NonNull Task<QuerySnapshot> task) { if (task.isSuccessful()){ for (DocumentSnapshot document: task.getResult()){ Log.d(LOG_TAG, document.getId() + " => " + document.getData()); } } else { Log.d(LOG_TAG, "error getting documents: ", task.getException()); } } } } }) 代码的第一部分,代表 Parallelism

Par.scala
  • import java.util.concurrent._ object Par { type Par[A] = ExecutorService => Future[A] def unit[A](a: A): Par[A] = (es: ExecutorService) => UnitFuture(a) private case class UnitFuture[A](get: A) extends Future[A] { def isDone = true def get(timeout: Long, units: TimeUnit): A = get def isCancelled = false def cancel(evenIfRunning: Boolean): Boolean = false } def map2[A, B, C](a: Par[A], b: Par[B])(f: (A, B) => C): Par[C] = (es: ExecutorService) => { val af = a(es) val bf = b(es) UnitFuture(f(af.get, bf.get)) } def fork[A](a: => Par[A]): Par[A] = (es: ExecutorService) => es.submit(new Callable[A] { def call: A = a(es).get }) def lazyUnit[A](a: => A): Par[A] = fork(unit(a)) def run[A](es: ExecutorService)(a: Par[A]): Future[A] = a(es) def asyncF[A, B](f: A => B): A => Par[B] = a => lazyUnit(f(a)) def map[A, B](pa: Par[A])(f: A => B): Par[B] = map2(pa, unit(()))((a, _) => f(a)) } 最简单的模型可能是Par[A],而ExecutorService => Future[A]只返回run
  • Future通过返回unit来提升并行计算的常量值,这是UnitFuture的一个简单实现,只包含一个常量值。
  • Future将两个并行计算的结果与二元函数结合起来。
  • map2标记并发评估的计算。在强制执行之前,评估实际上不会发生。这是它最简单,最自然的实现方式。即使它有问题,让我们先把它们放在一边。
  • fork将其未评估的参数包含在lazyUnit中,并将其标记为并发评估。
  • Par通过实际执行计算从run中提取值。
  • Par将任何函数asyncF转换为异步评估其结果的函数。

问题

A => B这个函数让我很困惑,因为它需要 lazy参数 ,稍后会在调用它时对其进行评估。然后我的问题更多地是关于何时应该使用这个fork,即当我们需要延迟评估时以及何时需要直接获得值时。

以下是本书的练习:

  

练习7.5   :编写此函数,称为序列。不需要其他原语。不要打电话。

     

fork

以下是答案(提供here)。

第一

def sequence[A](ps: List[Par[A]]): Par[List[A]]

上述代码与以下内容有何不同之处:

  def sequence_simple[A](l: List[Par[A]]): Par[List[A]] =
    l.foldRight[Par[List[A]]](unit(List()))((h, t) => map2(h, t)(_ :: _))

另外

  def sequence_simple[A](l: List[Par[A]]): Par[List[A]] =
    l.foldLeft[Par[List[A]]](unit(List()))((t, h) => map2(h, t)(_ :: _))

def sequenceRight[A](as: List[Par[A]]): Par[List[A]] = as match { case Nil => unit(Nil) case h :: t => map2(h, fork(sequenceRight(t)))(_ :: _) } def sequenceBalanced[A](as: IndexedSeq[Par[A]]): Par[IndexedSeq[A]] = fork { if (as.isEmpty) unit(Vector()) else if (as.length == 1) map(as.head)(a => Vector(a)) else { val (l,r) = as.splitAt(as.length/2) map2(sequenceBalanced(l), sequenceBalanced(r))(_ ++ _) } } 中,直接调用递归函数时使用sequenceRight。但是,在fork中,sequenceBalanced在整个函数体之外使用。

然后,差异或以上代码和以下内容(我们切换fork的位置)是什么:

fork

最后,鉴于上面定义的 def sequenceRight[A](as: List[Par[A]]): Par[List[A]] = fork { as match { case Nil => unit(Nil) case h :: t => map2(h, sequenceRight(t))(_ :: _) } } def sequenceBalanced[A](as: IndexedSeq[Par[A]]): Par[IndexedSeq[A]] = if (as.isEmpty) unit(Vector()) else if (as.length == 1) map(as.head)(a => Vector(a)) else { val (l,r) = as.splitAt(as.length/2) map2(fork(sequenceBalanced(l)), fork(sequenceBalanced(r)))(_ ++ _) } ,我们有以下功能:

sequence

我想知道,我是否也可以通过以下方式实现该功能,即应用开头定义的 def parMap[A,B](ps: List[A])(f: A => B): Par[List[B]] = fork { val fbs: List[Par[B]] = ps.map(asyncF(f)) sequence(fbs) } ?这个实现是lazyUnit懒惰吗?

lazyUnit(ps.map(f))

1 个答案:

答案 0 :(得分:1)

我并不完全理解你的怀疑。但我发现以下解决方案存在一个主要问题,

def parMapByLazyUnit[A, B](ps: List[A])(f: A => B): Par[List[B]] =
  lazyUnit(ps.map(f))

要了解问题,请查看def lazyUnit

def fork[A](a: => Par[A]): Par[A] =
  (es: ExecutorService) => es.submit(new Callable[A] {
    def call: A = a(es).get
  })

def lazyUnit[A](a: => A): Par[A] =
  fork(unit(a))

因此...... lazyUnit采用=> A类型的表达式,并将其提交给ExecutorService以进行评估。并将此并行计算的包装结果返回为Par[A]

parMap的每个元素ps: List[A],我们不仅要使用函数f: A => B评估相应的映射,还要进行这些评估in parallel。< / p>

但我们的解决方案lazyUnit(ps.map(f))会将整个{ ps.map(f) }评估作为单个任务提交给ExecutionService。这意味着我们不会并行执行。

我们需要做的是确保对于a中的每个元素ps: [A],函数f: A => B作为ExecutorService的单独任务执行。

现在,我们从实现中了解到,我们可以使用exp: => A运行lazyUnit(exp)类型的表达式来获取result: Par[A]

因此,对于a: A中的每个ps: List[A],我们都会这样做,

val parMappedTmp = ps.map( a => lazyUnit(f(a) ) )

// or

val parMappedTmp = ps.map( a => asyncF(f)(a) )

// or

val parMappedTmp = ps.map(asyncF(f))

但是,现在我们的parMappedTmpList[Par[B]],而我们需要Par[List[B]]

因此,您需要一个具有以下签名的函数来获得您想要的内容,

def sequence[A](ps: List[Par[A]]): Par[List[A]]

一旦拥有它,

val parMapped = sequence(parMappedTmp)