2012-04-20 10 views
14

Tengo una estructura de datos inmutables donde he anidado valores en mapas, así:Evitar la repetición usando lentes mientras profundamente copiar en asignar los valores de

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) 

Por otra parte tengo una lista de todos los oficios lo largo del día, y quiero generar la estructura TradingDay, donde

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

estoy tratando de averiguar cómo iba a actualizar esta estructura con lentes (véase el apéndice) doblando a través de mis operaciones:

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

Esto funciona; pero me pregunto si podría ser menos repetitivo (en términos de agregar un mapa y luego modificar el valor en la clave de un mapa. No parece mucho menos molesto que la copia profunda asociada.

Apéndice - las lentes de

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 para una pregunta con un apéndice – ziggystar

Respuesta

0

respuesta proporcionada por Jordan West (@_jrwest)

Es sólo un ligero cambio e implica introduciendo la siguiente conversión:

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

Entonces podemos usar esto como sigue:

(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

Ya existe una conversión implícita en la base de código de Scalaz. Solo falta putIfAbsent. ¿Cuida una solicitud de extracción? –

6

con

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

y manteniendo esta lente

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

y que definen esas lentes:

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 

usted podría conseguir esto:

(trades /: TradingDay()){ (trd, d) => (J.map(trd :: _).flatMap(_ => init)) ! d } 
+0

estoy confundido acerca de cómo podría trabajar; ¿Dónde se están agregando los mapas internos? Por ejemplo; la implementación obvia de 'G' devolverá un mapa vacío para' None'. ¿Alguna posibilidad de que desarrolles esto para demostrar que funciona? –

+0

Claro. Por cierto, parece que estás confiando más en la mónada estatal que en Lens. Las lentes son muy buenas cuando se accede a propiedades anidadas de tipos de datos algebraicos. Esos accesos no dependen de un estado externo. –

Cuestiones relacionadas