Избегание повторения с использованием объективов при глубоком копировании в значения карты
У меня есть неизменяемая структура данных, где у меня есть вложенные значения в Maps, например:
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))
}
Ответы
Ответ 1
Ответ предоставлен 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
}
Ответ 2
с
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 }