Scala方法对地图产生副作用并返回它 [英] Scala method to side effect on map and return it

查看:38
本文介绍了Scala方法对地图产生副作用并返回它的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

What is the best way to apply a function to each element of a Map and at the end return the same Map, unchanged, so that it can be used in further operations?

我想避免:

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

实现这样的语法:

myMap
  .mapEffectOnKV(effectfullFn)
  .foreach(println)

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

map is not what I'm looking for, because I have to specify what comes out of the map (as in the first code snippet), and I don't want to do that.

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

I want a special operation that knows/assumes that the map elements should be returned without change after the side-effect function has been executed.

事实上,这对我来说非常有用,我想把它用于MapArrayListSeq, Iterable... 大体思路是偷看元素做某事,然后自动返回这些元素.

In fact, this would be so useful to me, I'd like to have it for Map, Array, List, Seq, Iterable... The general idea is to peek at the elements to do something, then automatically return these elements.

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

The real case I'm working on looks like this:

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

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

Once I have calculated the statistics for each shard, I want to append the side effect of saving them to disk, and then just return those statistics, without having to create a val and having that val's name be the last statement in the function, e.g.:

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 中是否已经有一些东西可以实现这一点.

It's probably not very hard to implement, but I was wondering if there was something in Scala already for that.

推荐答案

函数不能按定义有效,所以我不希望在 Scala-lib 中有任何方便.但是,您可以编写一个包装器:

Function cannot be effectful by definition, so I wouldn't expect anything convenient in scala-lib. However, you can write a wrapper:

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)

你也可以定义一个隐式:

You can also define an implicit:

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等等,如果你需要它:

To abstract more, you can define this implicit on TraversableOnce, so it would be applicable to List, Set and so on if you need it:

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",因为 Col[_] <: TraversableOnce[_] 不起作用(Scala 2.12.1),所以我必须为此使用证据.

It's more useful, but requires some "mamba-jumbo" with types, as Coll[_] <: TraversableOnce[_] doesn't work (Scala 2.12.1), so I had to use an evidence for that.

您也可以尝试 CanBuildFrom 方法:如何用我自己的通用地图丰富 TraversableOnce?

You can also try CanBuildFrom approach: How to enrich a TraversableOnce with my own generic map?

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

Overall recommendation about dealing with passthrough side-effects on iterators is to use Streams (scalaz/fs2/monix) and Task, so they've got an observe (or some analog of it) function that does what you want in async (if needed) way.

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

My answer before you provided example of what you want

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

You can represent effectful computation without side-effects and have distinct values that represent state before and after:

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)

这篇关于Scala方法对地图产生副作用并返回它的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆