使用镜头避免重复,同时将数据深度复制到Map值中

14

我有一个不可变的数据结构,在其中我有嵌套在Map中的值,如下:

case class TradingDay(syms: Map[String, SymDay] = Map.empty)
case class SymDay(sym: String, traders: Map[String, TraderSymDay] = Map.empty)
case class TraderSymDay(trader: String, sym: String, trades: List[Trade] = Nil)

我有一份当天所有交易清单,我想生成TradingDay结构,其中

case class Trade(sym: String, trader: String, qty: Int)

我正在尝试通过使用镜头(请参见附录)来更新这个结构,方法是通过我的交易进行折叠:

(TradingDay() /: trades) { (trd, d) =>
  def sym = trd.sym
  def trader = trd.trader
  import TradingDay._
  import SymDay._
  import TraderSymDay._
  val mod =
    for {
      _ <- (Syms member sym).mods(
             _ orElse some(SymDay(sym)))
      _ <- (Syms at sym andThen Traders member trader).mods(
             _ orElse some(TraderSymDay(trader, sym)))
      _ <- (Syms at sym andThen (Traders at trader) andThen Trades).mods(
             trd :: _)
      x <- init
    } yield x
  mod ! d
}

这个方法可行,但我在想是否有更简便的方式(例如只需添加一次到映射表中并修改映射表中键的值)。这似乎并没有比深拷贝更方便。
附录 - 镜头
object TradingDay {
  val Syms = Lens[TradingDay, Map[String, SymDay]](_.syms, (d, s) => d.copy(syms = s))
}

object SymDay {
  val Traders = Lens[SymDay, Map[String, TraderSymDay]](_.traders, (d, t) => d.copy(traders = t))
}

object TraderSymDay  {
   val Trades = Lens[TraderSymDay, List[Trade]](_.trades, (d, f) => d.copy(trades = f))
}

3
+1 是指在问题后面附加一个附录。 - ziggystar
2个回答

6

使用

type @>[A,B] = Lens[A, B]

并且通过保持这个镜头
val Syms : Lens[TradingDay, Map[String, SymDay]]

并定义这些镜头:

val F : Map[String, SymDay] @> Option[SymDay] = ...
val G : Option[SymDay] @> Map[String, TraderSymDay] = ...
val H : Map[String, TraderSymDay] @> Option[TraderSymDay] = ...
val I : Option[TraderSymDay] @> List[Trade] = ...

val J: TradingDay @> List[Trade] = Syms >=> F >=> G >=> H >=> I

您可以获得以下内容:
(trades /: TradingDay()){ (trd, d) => (J.map(trd :: _).flatMap(_ => init)) ! d }

我对它如何工作感到困惑;内部映射被添加到哪里了?例如,明显的 G 的实现将对 None 返回一个空映射。你有机会详细解释一下以证明它是可行的吗? - oxbow_lakes
当然。顺便说一句,看起来你更多地依赖于State Monad而不是Lens。当访问代数数据类型的嵌套属性时,Lens非常好用。这些访问不依赖于外部状态。 - Yo Eight

0

Jordan West提供的答案(@_jrwest

这只是一个轻微的更改,涉及引入以下转换:

implicit def myMapLens[S,K,V] = MyMapLens[S,K,V](_)
case class MyMapLens[S,K,V](lens: Lens[S,Map[K,V]]) {
  def putIfAbsent(k: K, v: => V) 
    = lens.mods(m => m get k map (_ => m) getOrElse (m + (k -> v)))
}

然后我们可以按照以下方式使用:

(TradingDay() /: trades) { (d, trade) =>
  def sym = trade.sym
  def trader = trade.trader
  def traders = Syms at sym andThen Traders
  def trades = Syms at sym andThen (Traders at trader) andThen Trades
  val upd =
    for {
      _ <- Syms putIfAbsent (sym, SymDay(sym))
      _ <- traders putIfAbsent (trader, TraderSymDay(trader, sym))
      _ <- trades.mods(trade :: _)
    } yield ()
  upd ~> d
}

1
Scalaz 代码库中已经存在这样的隐式转换。只有 putIfAbsent 缺失了。是否考虑提交一个 pull request? - Yo Eight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接