2015-10-21 3 views
7

Некоторые вложенные классы случаев и поле addresses является Seq[Address]:Как изменить эти вложенные классы case с полями «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, ...) 

У меня есть сотрудник:

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

Он имеет 3 адреса.

И я хочу использовать прописные буквы, начинающиеся с буквы «b». Мой код бардак, как следующее:

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 работник затем:

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

Я ищу способ улучшить его, и не может найти. Даже попробовал Monocle, но не может применить его к этой проблеме.

Есть ли способ сделать это лучше?


PS: Есть два ключевых требования:

  1. используют только неизменные данные
  2. не теряют других существующих полей

ответ

13

Как Петр Neyens указывает, НСБ бесформенные работает очень хорошо здесь, но это изменит всеStreet значения в дереве, которые не всегда могут будь тем, кем хочешь быть. Если вам нужно больше контроля над тропой, Монокль может помочь:

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 Трюффо указывает в редактировании, вы можете сделать это еще более кратким (но менее общий) путем создания линзой вплоть до первый символ названия улицы:

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 
} 

Есть символические операторы, которые сделали бы определение выше немного более кратким, но я предпочитаю не-символьные версии.

А потом (с результатом переформатирован для наглядности):

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

Обратите внимание, что, как и в бесформенном ответе, вы должны изменить свое Employee определения использовать List вместо Seq, или если вы не Не хотите менять свою модель, вы можете построить это преобразование в Lens с Iso[Seq[A], List[A]].

8

Если вы открыты для замены addresses в Company от Seq до List, вы можете использовать «Лом вашей котельной» из бесформенного (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"))))) 

Вы можете создать полиморфный функцию, которая капитализирует имя Street если имя начинается с «Ъ».

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

Что вы можете использовать как:

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

Спасибо так много !!! Это действительно здорово. Наконец-то я получил шанс узнать, насколько мощным бесформенным! – Freewind

+3

Хороший ответ, но см. Мой для предупреждения (это преобразит _any_ названия улиц в структуре данных). –

2

Посмотрите на quicklens

Вы могли бы сделать это так

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