2015-10-21 6 views
7

Einige eingebettete Fall-Klassen und das Feld addresses ist ein Seq[Address]:Wie ändert man diese geschachtelten Fall-Klassen mit "Seq" -Feldern?

// ... means other fields 
case class Street(name: String, ...) 
case class Address(street: Street, ...) 
case class Company(addresses: Seq[Address], ...) 
case class Employee(company: Company, ...) 

ich einen Mitarbeiter haben:

val employee = Employee(Company(Seq(
    Address(Street("aaa street")), 
    Address(Street("bbb street")), 
    Address(Street("bpp street"))))) 

Es verfügt über 3 Adressen.

Und ich möchte, dass die Straßen mit „b“ nur beginnen zu nutzen. Mein Code ist Chaos wie folgt vor:

val modified = employee.copy(company = employee.company.copy(addresses = 
    employee.company.addresses.map { address => 
     address.copy(street = address.street.copy(name = { 
      if (address.street.name.startsWith("b")) { 
      address.street.name.capitalize 
      } else { 
      address.street.name 
      } 
     })) 
     })) 

Der modified Mitarbeiter ist dann:

Employee(Company(List(
    Address(Street(aaa street)), 
    Address(Street(Bbb street)), 
    Address(Street(Bpp street))))) 

ich nach einem Weg, um es zu verbessern, und nicht finden können. Selbst versucht Monocle, kann es aber nicht auf dieses Problem anwenden.

Gibt es eine Möglichkeit, es besser zu machen?


PS: Es gibt zwei wesentliche Anforderungen:

  1. Verwendung nur unveränderlichen Daten
  2. verlieren nicht den anderen bestehenden Feldern

Antwort

13

Wie Peter Neyens weist darauf hin, Shapeless der SYB wirklich schön hier funktioniert, aber es wird alleStreet Werte im Baum ändern, welche nicht immer Sei was du willst. Wenn Sie mehr Kontrolle über den Weg benötigen, Monocle helfen:

import monocle.Traversal 
import monocle.function.all._, monocle.macros._, monocle.std.list._ 

val employeeStreetNameLens: Traversal[Employee, String] = 
    GenLens[Employee](_.company).composeTraversal(
    GenLens[Company](_.addresses) 
     .composeTraversal(each) 
     .composeLens(GenLens[Address](_.street)) 
     .composeLens(GenLens[Street](_.name)) 
) 

    val capitalizer = employeeStreeNameLens.modify { 
    case s if s.startsWith("b") => s.capitalize 
    case s => s 
    } 

Als Julien Truffaut Punkte in einem bearbeiten, können Sie dies noch prägnanter machen (aber weniger allgemein) durch eine Linse den ganzen Weg zu der Schaffung erste Zeichen des Straßennamens:

import monocle.std.string._ 

val employeeStreetNameFirstLens: Traversal[Employee, Char] = 
    GenLens[Employee](_.company.addresses) 
    .composeTraversal(each) 
    .composeLens(GenLens[Address](_.street.name)) 
    .composeOptional(headOption) 

val capitalizer = employeeStreetNameFirstLens.modify { 
    case 'b' => 'B' 
    case s => s 
} 

Es gibt symbolische Operatoren, die die Definitionen über etwas prägnanter machen würde, aber ich ziehe die nicht-symbolischen Versionen.

Und dann (mit dem Ergebnis, aus Gründen der Klarheit neu formatiert):

scala> capitalizer(employee) 
res3: Employee = Employee(
    Company(
    List(
     Address(Street(aaa street)), 
     Address(Street(Bbb street)), 
     Address(Street(Bpp street)) 
    ) 
) 
) 

Beachten Sie, dass, wie in der Shapeless Antwort, werden Sie Ihre Employee Definition ändern müssen List statt Seq zu verwenden, oder wenn Sie don Wenn Sie Ihr Modell nicht ändern möchten, können Sie diese Umwandlung in die Lens mit einer Iso[Seq[A], List[A]] erstellen.

8

Wenn Sie offen sind, um die addresses ersetzt in Company von Seq zu List, können Sie „Schrott Ihre Boilerplate“ von unförmigen verwenden (example).

import shapeless._, poly._ 

case class Street(name: String) 
case class Address(street: Street) 
case class Company(addresses: List[Address]) 
case class Employee(company: Company) 

val employee = Employee(Company(List(
    Address(Street("aaa street")), 
    Address(Street("bbb street")), 
    Address(Street("bpp street"))))) 

Sie können eine polymorphe Funktion erstellen, die den Namen eines Street kapitalisiert, wenn der Name mit einem „b“ beginnt.

object capitalizeStreet extends ->(
    (s: Street) => { 
    val name = if (s.name.startsWith("b")) s.name.capitalize else s.name 
    Street(name) 
    } 
) 

Welche können Sie verwenden, wie:

val afterCapitalize = everywhere(capitalizeStreet)(employee) 
// Employee(Company(List(
// Address(Street(aaa street)), 
// Address(Street(Bbb street)), 
// Address(Street(Bpp street))))) 
+1

Dank tun könnte !!! Das ist echt cool. Ich habe endlich eine Chance zu wissen, wie kraftvoll formlos ist! – Freewind

+3

Schöne Antwort, aber sehen Sie meine für eine Warnung (dies wird _any_ Straße Namen in der Datenstruktur transformieren). –

2

Werfen Sie einen Blick auf quicklens

Sie es so viel wie dieses

import com.softwaremill.quicklens._ 

case class Street(name: String) 
case class Address(street: Street) 
case class Company(address: Seq[Address]) 
case class Employee(company: Company) 
object Foo { 
    def foo(e: Employee) = { 
    modify(e)(_.company.address.each.street.name).using { 
     case name if name.startsWith("b") => name.capitalize 
     case name => name 
    } 
    } 
}