【问题标题】:Scala method to side effect on map and return itScala方法对地图产生副作用并返回它
【发布时间】:2017-03-01 03:31:12
【问题描述】:

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

我想避免:

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

实现这样的语法:

myMap
  .mapEffectOnKV(effectfullFn)
  .foreach(println)

map 不是我要找的,因为我必须指定地图中的内容(如在第一个代码 sn-p 中),我不想这样做。

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

事实上,这对我很有用,我想拥有它以供MapArrayListSeqIterable... 总体思路是偷看在元素处做某事,然后自动返回这些元素。

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

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

计算完每个分片的统计信息后,我想附加将它们保存到磁盘的副作用,然后只返回这些统计信息,而无需创建 val 并拥有 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

这可能不是很难实现,但我想知道 Scala 中是否已经为此提供了一些东西。

【问题讨论】:

  • How to iterate scala map?的可能重复
  • 不 - 我认为它不同。我正在尝试在地图上的一系列操作中添加具有副作用的内容。
  • 我建议不要这样做。您将不必要地多次遍历地图。只需迭代一次,然后对单次迭代中的键值对执行您需要做的任何事情。
  • 这有时被称为tap
  • @Yawar - 我添加了我的真实案例。我不认为我会重复多次。

标签: scala


【解决方案1】:

根据定义,函数不能有效,所以我不希望在 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)

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

你也可以试试CanBuildFrom 方法:How to enrich a 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)

【讨论】:

  • .map(tap !! .tap 怎么样?
  • @Frank 已经添加了这个(当你评论时我正在编辑答案:))。然而,原始水龙头更通用,因为它不关心容器的类型。
  • implicit TapMap 很好 - 它可以推广到任何可以迭代的东西吗?
  • TraversableOnce,是的,但作者确实只询问了地图:)
  • 不完全是——作者说他希望为一堆数据结构提供该功能:-)
【解决方案2】:

看起来您所追求的概念类似于 Unix tee 实用程序——接受一个输入并将其引导到两个不同的输出。 (tee 得名于字母“T”的形状,它看起来像一个 管道从左到右,另一条线向下分支。) 这是 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 是非常通用的——它可以做一个有效的 对任意值进行操作,然后返回原来传入的值。

【讨论】:

  • Hmmmm - 在 foreach 中滚动怎么样,让语法变得更轻?这个很笼统,从这个角度来看非常好,但假设A 可以迭代我就可以了。
【解决方案3】:

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

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

如果你想链接这个操作,你可以使用map

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

【讨论】:

    猜你喜欢
    • 2015-06-20
    • 2018-01-17
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 2012-09-19
    • 2015-05-27
    • 2011-11-03
    • 1970-01-01
    相关资源
    最近更新 更多