Scala方法对地图产生副作用并返回它

Fra*_*ank 5 scala

将函数应用于 a 的每个元素Map并最终返回相同的Map,不变的,以便它可以用于进一步的操作的最佳方法是什么?

我想避免:

myMap.map(el => {
  effectfullFn(el)
  el
})
Run Code Online (Sandbox Code Playgroud)

实现这样的语法:

myMap
  .mapEffectOnKV(effectfullFn)
  .foreach(println)
Run Code Online (Sandbox Code Playgroud)

map 不是我要找的,因为我必须指定地图中出现的内容(如第一个代码片段中所示),而我不想这样做。

我想要一个特殊的操作,它知道/假设在执行副作用函数后应该不加更改地返回地图元素。

事实上,这对我来说非常有用,我想把它用于Map, Array, List, Seq,Iterable ... 总的想法是偷看元素做某事,然后自动返回这些元素。

我正在处理的真实案例如下所示:

 calculateStatistics(trainingData, indexMapLoaders)
   .superMap { (featureShardId, shardStats) =>
      val outputDir = summarizationOutputDir + "/" + featureShardId
      val indexMap = indexMapLoaders(featureShardId).indexMapForDriver()
      IOUtils.writeBasicStatistics(sc, shardStats, outputDir, indexMap)
    }
Run Code Online (Sandbox Code Playgroud)

一旦我计算了每个分片的统计信息,我想附加将它们保存到磁盘的副作用,然后只返回这些统计信息,而不必创建 aval并将其val名称作为函数中的最后一个语句,例如:

val stats = calculateStatistics(trainingData, indexMapLoaders)
stats.foreach { (featureShardId, shardStats) =>
  val outputDir = summarizationOutputDir + "/" + featureShardId
  val indexMap = indexMapLoaders(featureShardId).indexMapForDriver()
  IOUtils.writeBasicStatistics(sc, shardStats, outputDir, indexMap)
}
stats
Run Code Online (Sandbox Code Playgroud)

这可能不是很难实现,但我想知道 Scala 中是否已经有一些东西可以实现。

dk1*_*k14 5

根据定义,函数不能有效,所以我不期望 scala-lib 中有任何方便的东西。但是,您可以编写一个包装器:

def tap[T](effect: T => Unit)(x: T) = {
  effect(x)
  x
}
Run Code Online (Sandbox Code Playgroud)

例子:

scala> Map(1 -> 1, 2 -> 2)
         .map(tap(el => el._1 + 5 -> el._2))
         .foreach(println)
(1,1)
(2,2)
Run Code Online (Sandbox Code Playgroud)

您还可以定义隐式:

implicit class TapMap[K,V](m: Map[K,V]){
  def tap(effect: ((K,V)) => Unit): Map[K,V] = m.map{x =>
    effect(x)
    x
  }
}
Run Code Online (Sandbox Code Playgroud)

例子:

scala> Map(1 -> 1, 2 -> 2).tap(el => el._1 + 5 -> el._2).foreach(println)
(1,1)
(2,2)
Run Code Online (Sandbox Code Playgroud)

为了进行更多抽象,您可以在 上定义此隐式TraversableOnce,因此它将适用于ListSet依此类推(如果需要):

implicit class TapTraversable[Coll[_], T](m: Coll[T])(implicit ev: Coll[T] <:< TraversableOnce[T]){
  def tap(effect: T => Unit): Coll[T] = {
    ev(m).foreach(effect)
    m
  }
}

scala> List(1,2,3).tap(println).map(_ + 1)
1
2
3
res24: List[Int] = List(2, 3, 4)

scala> Map(1 -> 1).tap(println).toMap //`toMap` is needed here for same reasons as it needed when you do `.map(f).toMap`
(1,1)
res5: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1)

scala> Set(1).tap(println)
1
res6: scala.collection.immutable.Set[Int] = Set(1)
Run Code Online (Sandbox Code Playgroud)

它更有用,但需要一些带有类型的“mamba-jumbo”,因为它Coll[_] <: TraversableOnce[_]不起作用(Scala 2.12.1),所以我必须为此使用证据。

您还可以尝试CanBuildFrom方法:How to丰富 TraversableOnce with my own generic map?


关于处理迭代器上的直通副作用的总体建议是使用Streams (scalaz/fs2/monix) 和Task,因此它们有一个observe(或它的一些类似)函数,可以以异步(如果需要)方式执行您想要的操作。


在你提供你想要的例子之前我的回答

您可以表示有效的计算而没有副作用,并且具有表示之前和之后状态的不同值:

scala> val withoutSideEffect = Map(1 -> 1, 2 -> 2)
withoutSideEffect: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1, 2 -> 2)                                                                       

scala> val withSideEffect = withoutSideEffect.map(el => el._1 + 5 -> (el._2 + 5))
withSideEffect: scala.collection.immutable.Map[Int,Int] = Map(6 -> 6, 7 -> 7)

scala> withoutSideEffect //unchanged
res0: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1, 2 -> 2)

scala> withSideEffect //changed
res1: scala.collection.immutable.Map[Int,Int] = Map(6 -> 6, 7 -> 7)
Run Code Online (Sandbox Code Playgroud)