我一直在阅读这本书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))
答案 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))
但是,现在我们的parMappedTmp
是List[Par[B]]
,而我们需要Par[List[B]]
因此,您需要一个具有以下签名的函数来获得您想要的内容,
def sequence[A](ps: List[Par[A]]): Par[List[A]]
一旦拥有它,
val parMapped = sequence(parMappedTmp)