14

I have an immutable data structure where I have nested values in Maps, like so:

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)

Separately I have a list of all trades over the day, and I want to generate the TradingDay structure, where

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

I am trying to figure out how I would update this structure with lenses (see appendix) by folding through my trades:

(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
}

This works; but I'm wondering whether I could be less repetitive (in terms of adding to a map and then modifying the value at the key of a map. It doesn't seem that much less annoying than the associated deep-copy.

Appendix - the lenses

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))
}
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449

2 Answers2

6

with

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

and by keeping this lens

val Syms : Lens[TradingDay, Map[String, SymDay]]

and defining those lenses:

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

you could get this:

(trades /: TradingDay()){ (trd, d) => (J.map(trd :: _).flatMap(_ => init)) ! d }
Yo Eight
  • 467
  • 2
  • 5
  • I'm confused about how that could work; where are the inner maps being added to? For example; the obvious implementation of `G` will return an empty map for `None`. Any chance of you fleshing this out to prove it works? – oxbow_lakes Apr 21 '12 at 17:16
  • Sure. BTW, it seems you're relying more on the State monad than Lens. Lenses are very good when accessing nested properties of algebraic data types. Those accesses do not depend on an external state. – Yo Eight Apr 21 '12 at 19:43
0

Answer provided by Jordan West (@_jrwest)

It's only a slight change and involves introducing the following conversion:

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)))
}

Then we can use this as follows:

(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
}
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • 1
    There's already such implicit conversion in Scalaz codebase. Only putIfAbsent is missing. Care for a pull request ? – Yo Eight Apr 25 '12 at 21:08