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

时间:2017-03-01 03:31:13

标签: scala

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

我想避免:

myMap.map(el => {
  effectfullFn(el)
  el
})

实现这样的语法:

myMap
  .mapEffectOnKV(effectfullFn)
  .foreach(println)

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

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

事实上,这对我来说非常有用,我想为MapArrayListSeq,{{ 1}} ...一般的想法是窥视要做某事的元素,然后自动返回这些元素。

我正在研究的真实案例如下:

Iterable

一旦我计算了每个分片的统计信息,我想附加将它们保存到磁盘的副作用,然后只返回这些统计信息,而不必创建 calculateStatistics(trainingData, indexMapLoaders) .superMap { (featureShardId, shardStats) => val outputDir = summarizationOutputDir + "/" + featureShardId val indexMap = indexMapLoaders(featureShardId).indexMapForDriver() IOUtils.writeBasicStatistics(sc, shardStats, outputDir, indexMap) } 并具有{{1} } name是函数中的最后一个语句,例如:

val

它可能不是很难实现,但我想知道Scala中是否已经存在某些内容。

3 个答案:

答案 0 :(得分:2)

根据定义,函数不能有效,因此我不希望在scala-lib中有任何方便。但是,您可以编写包装器:

def tap[T](effect: T => Unit)(x: T) = {
  effect(x)
  x
}

示例:

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

您还可以定义隐式:

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

示例:

scala> Map(1 -> 1, 2 -> 2).tap(el => el._1 + 5 -> el._2).foreach(println)
(1,1)
(2,2)

要抽象更多内容,您可以在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)

它更有用,但需要一些&#34; mamba-jumbo&#34;由于Coll[_] <: TraversableOnce[_]不起作用的类型(Scala 2.12.1),所以我不得不使用证据。

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

关于处理迭代器的直通副作用的总体建议是使用Stream s(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)

答案 1 :(得分:1)

看起来你所追求的概念类似于Unix tee 实用程序 - 获取输入并将其指向两个不同的输出。 (tee 从字母&#39; T&#39;的形状得到它的名字,它看起来像一个 管道从左到右,另一条管线向下分支。) 这是Scala版本:

package object mypackage {
  implicit class Tee[A](a: A) extends AnyVal {
    def tee(f: A => Unit): A = { f(a); a }
  }
}

有了这个,我们可以做到:

calculateStatistics(trainingData, indexMapLoaders) tee { stats =>
  stats foreach { case (featureShardId, shardStats) =>
    val outputDir = summarizationOutputDir + "/" + featureShardId
    val indexMap = indexMapLoaders(featureShardId).indexMapForDriver()
    IOUtils.writeBasicStatistics(sc, shardStats, outputDir, indexMap)
  }
}

请注意,根据定义,Tee非常通用 - 它可以有效 对任何值进行操作,然后返回原始传入的值。

答案 2 :(得分:0)

使用您的有效功能调用foreach上的Map。原始Map不会被更改,因为scala中的Maps是不可变的。

val myMap = Map(1 -> 1)
myMap.foreach(effectfullFn)

如果您尝试将此操作链接起来,可以使用map

myMap.map(el => {
    effectfullFn(el)
    el
})