2015-10-21 50 views
7

Niektóre zagnieżdżone klasy przypadków i pole addresses jest Seq[Address]:Jak zmodyfikować te zagnieżdżone klasy przypadków za pomocą pól "Seq"?

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

mam pracownika:

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

Posiada 3 adresy.

I chcę zacząć na wielką skalę na ulicach zaczynając od "b". Mój kod jest bałagan jak następuje:

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

modified pracownik jest wówczas:

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

Szukam sposobu, aby go poprawić, a nie może znaleźć. Nawet próbował Monocle, ale nie może zastosować go do tego problemu.

Czy jest jakiś sposób, aby to poprawić?


PS: istnieją dwa podstawowe wymagania:

  1. wykorzystanie danych tylko niezmienne
  2. nie stracić innych istniejących pól

Odpowiedz

13

Jak Peter Neyens zaznacza, bezkształtne za SYB działa bardzo ładnie tutaj, ale będzie modyfikować wszystkieStreet wartości w drzewie, które nie zawsze mogą bądź kim chcesz. Jeśli potrzebujesz większej kontroli nad ścieżką, Monocle może pomóc:

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 
    } 

Julien Truffaut zaznacza w edycji, można zrobić to nawet bardziej zwięzły (ale mniej ogólnie) tworząc soczewce całą drogę do pierwszy znak nazwy ulicy:

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 
} 

są operatorzy symboliczne, które sprawiają, że powyższe definicje trochę bardziej zwięzły, ale wolę wersje non-symbolicznych.

A potem (z wynikiem przeformatowanym dla jasności):

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

Zauważ, że jako odpowiedź bezkształtne, trzeba zmienić definicję Employee używać List zamiast Seq, lub jeśli nie nie chcesz zmienić swojego modelu, możesz zbudować tę transformację w Lens z Iso[Seq[A], List[A]].

8

Jeśli jesteś otwarty na wymianie addresses w Company od Seq do List, możesz użyć "Złom twój Kocioł" z shapeless (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"))))) 

Można utworzyć funkcję, która polimorficzny kapitalizuje nazwę Street jeśli nazwa zaczyna się od „B”.

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

które można wykorzystać jako:

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

Dziękuję bardzo !!! To jest naprawdę fajne. W końcu miałem szansę dowiedzieć się, jak potężny jest bezkształtny! – Freewind

+3

Dobra odpowiedź, ale zobacz moje ostrzeżenie (to zmieni nazwy ulic w strukturze danych). –

2

Spójrz na quicklens

Można zrobić to tak

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