Jac*_*nig 5 functional-programming scala
我经常发现自己需要collects在我想要在一次遍历中进行多次收集的地方进行链接。对于与任何集合都不匹配的东西,我还想返回一个“剩余部分”。
例如:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String, age: Int) extends Animal
val animals: List[Animal] =
List(Cat("Bob"), Dog("Spot", 3), Cat("Sally"), Dog("Jim", 11))
// Normal way
val cats: List[Cat] = animals.collect { case c: Cat => c }
val dogAges: List[Int] = animals.collect { case Dog(_, age) => age }
val rem: List[Animal] = Nil // No easy way to create this without repeated code
Run Code Online (Sandbox Code Playgroud)
这真的不是很好,它需要多次迭代并且没有合理的方法来计算余数。我可以写一个非常复杂的折叠来解决这个问题,但这真的很讨厌。
相反,我通常会选择与您在 a 中拥有的逻辑非常相似的突变fold:
import scala.collection.mutable.ListBuffer
// Ugly, hide the mutation away
val (cats2, dogsAges2, rem2) = {
// Lose some benefits of type inference
val cs = ListBuffer[Cat]()
val da = ListBuffer[Int]()
val rem = ListBuffer[Animal]()
// Bad separation of concerns, I have to merge all of my functions
animals.foreach {
case c: Cat => cs += c
case Dog(_, age) => da += age
case other => rem += other
}
(cs.toList, da.toList, rem.toList)
}
Run Code Online (Sandbox Code Playgroud)
我不喜欢这一点,它具有更糟糕的类型推断和关注点分离,因为我必须合并所有不同的部分函数。它还需要很多行代码。
我想要的是一些有用的模式,比如collect返回余数的 a (我partitionMap在 2.13 中授予new 这样做,但更丑陋)。我也可以使用某种形式的pipe或map用于操作元组的一部分。以下是一些组成的实用程序:
implicit class ListSyntax[A](xs: List[A]) {
import scala.collection.mutable.ListBuffer
// Collect and return remainder
// A specialized form of new 2.13 partitionMap
def collectR[B](pf: PartialFunction[A, B]): (List[B], List[A]) = {
val rem = new ListBuffer[A]()
val res = new ListBuffer[B]()
val f = pf.lift
for (elt <- xs) {
f(elt) match {
case Some(r) => res += r
case None => rem += elt
}
}
(res.toList, rem.toList)
}
}
implicit class Tuple2Syntax[A, B](x: Tuple2[A, B]){
def chainR[C](f: B => C): Tuple2[A, C] = x.copy(_2 = f(x._2))
}
Run Code Online (Sandbox Code Playgroud)
现在,我可以用一种可以在单次遍历中完成的方式(使用惰性数据结构)来编写它,并且遵循功能性的、不可变的实践:
// Relatively pretty, can imagine lazy forms using a single iteration
val (cats3, (dogAges3, rem3)) =
animals.collectR { case c: Cat => c }
.chainR(_.collectR { case Dog(_, age) => age })
Run Code Online (Sandbox Code Playgroud)
我的问题是,有这样的模式吗?它闻起来有点像 Cats、FS2 或 ZIO 这样的库中的东西,但我不确定它可能叫什么。
代码示例的 Scastie 链接:https ://scastie.scala-lang.org/Egz78fnGR6KyqlUTNTv9DQ
我想看看 afold()会有多“讨厌” 。
val (cats
,dogAges
,rem) = animals.foldRight((List.empty[Cat]
,List.empty[Int]
,List.empty[Animal])) {
case (c:Cat, (cs,ds,rs)) => (c::cs, ds, rs)
case (Dog(_,d),(cs,ds,rs)) => (cs, d::ds, rs)
case (r, (cs,ds,rs)) => (cs, ds, r::rs)
}
Run Code Online (Sandbox Code Playgroud)
我想是旁观者的眼睛。
定义几个实用程序类来帮助您解决这个问题怎么样?
case class ListCollect[A](list: List[A]) {
def partialCollect[B](f: PartialFunction[A, B]): ChainCollect[List[B], A] = {
val (cs, rem) = list.partition(f.isDefinedAt)
new ChainCollect((cs.map(f), rem))
}
}
case class ChainCollect[A, B](tuple: (A, List[B])) {
def partialCollect[C](f: PartialFunction[B, C]): ChainCollect[(A, List[C]), B] = {
val (cs, rem) = tuple._2.partition(f.isDefinedAt)
ChainCollect(((tuple._1, cs.map(f)), rem))
}
}
Run Code Online (Sandbox Code Playgroud)
ListCollect只是为了启动链,并ChainCollect获取先前的余数(元组的第二个元素)并尝试将 a 应用于PartialFunction它,创建一个新ChainCollect对象。我不是特别喜欢这种生成的嵌套元组,但是如果您使用 Shapeless ,也许可以让它看起来更好一些HList。
val ((cats, dogs), rem) = ListCollect(animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.tuple
Run Code Online (Sandbox Code Playgroud)
Dotty 的*:类型让这变得更容易一些:
opaque type ChainResult[Prev <: Tuple, Rem] = (Prev, List[Rem])
extension [P <: Tuple, R, N](chainRes: ChainResult[P, R]) {
def partialCollect(f: PartialFunction[R, N]): ChainResult[List[N] *: P, R] = {
val (cs, rem) = chainRes._2.partition(f.isDefinedAt)
(cs.map(f) *: chainRes._1, rem)
}
}
Run Code Online (Sandbox Code Playgroud)
这确实最终导致输出被反转,但它没有我之前的方法中那种丑陋的嵌套:
val ((owls, dogs, cats), rem) = (EmptyTuple, animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.partialCollect { case Owl(wisdom) => wisdom }
/* more animals */
case class Owl(wisdom: Double) extends Animal
case class Fly(isAnimal: Boolean) extends Animal
val animals: List[Animal] =
List(Cat("Bob"), Dog("Spot", 3), Cat("Sally"), Dog("Jim", 11), Owl(200), Fly(false))
Run Code Online (Sandbox Code Playgroud)
如果您仍然不喜欢这样,您可以随时定义更多辅助方法来反转元组,在列表上添加扩展而无需从 EmptyTuple 开始,等等。
//Add this to the ChainResult extension
def end: Reverse[List[R] *: P] = {
def revHelp[A <: Tuple, R <: Tuple](acc: A, rest: R): RevHelp[A, R] =
rest match {
case EmptyTuple => acc.asInstanceOf[RevHelp[A, R]]
case h *: t => revHelp(h *: acc, t).asInstanceOf[RevHelp[A, R]]
}
revHelp(EmptyTuple, chainRes._2 *: chainRes._1)
}
//Helpful types for safety
type Reverse[T <: Tuple] = RevHelp[EmptyTuple, T]
type RevHelp[A <: Tuple, R <: Tuple] <: Tuple = R match {
case EmptyTuple => A
case h *: t => RevHelp[h *: A, t]
}
Run Code Online (Sandbox Code Playgroud)
现在你可以这样做:
val (cats, dogs, owls, rem) = (EmptyTuple, animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.partialCollect { case Owl(wisdom) => wisdom }
.end
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
173 次 |
| 最近记录: |