2015-10-21 16 views
7

Một số lớp trường hợp lồng nhau và lĩnh vực addresses là một Seq[Address]:Làm thế nào để sửa đổi trường hợp lồng nhau này với trường "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, ...) 

tôi có một nhân viên:

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

Nó có 3 địa chỉ.

Và tôi muốn tận dụng các đường phố chỉ bắt đầu bằng "b". Mã của tôi là mớ hỗn độn như sau:

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

Nhân viên modified là sau đó:

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

Tôi đang tìm kiếm một cách để cải thiện nó, và không thể tìm thấy một. Thậm chí đã thử Monocle nhưng không thể áp dụng cho sự cố này.

Có cách nào để cải thiện nó không?


PS: có hai yêu cầu quan trọng:

  1. sử dụng dữ liệu chỉ bất di bất dịch
  2. không bị mất các lĩnh vực khác hiện có

Trả lời

13

Như Peter Neyens chỉ ra, SYB hình thù của hoạt động thực sự độc đáo ở đây, nhưng nó sẽ thay đổi tất cảStreet giá trị trong cây, trong đó có thể không phải lúc nào là những gì bạn muốn. Nếu bạn cần kiểm soát tốt hơn các con đường, Monocle có thể giúp:

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 
    } 

Như Julien Truffaut chỉ ra trong một chỉnh sửa, bạn có thể làm cho điều này thậm chí ngắn gọn hơn (nhưng ít nói chung) bằng cách tạo ra một ống kính tất cả các cách để các ký tự đầu tiên của tên đường phố:

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 
} 

có nhà khai thác mang tính biểu tượng đó sẽ làm cho các định nghĩa trên súc tích hơn một chút, nhưng tôi thích các phiên bản không mang tính biểu tượng.

Và rồi (với kết quả định dạng lại cho rõ ràng):

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

Lưu ý rằng khi trong câu trả lời hình thù, bạn sẽ cần phải thay đổi định nghĩa Employee bạn để sử dụng List thay vì Seq, hoặc nếu quý don Không muốn thay đổi mô hình của bạn, bạn có thể tạo phép biến đổi đó thành Lens với một Iso[Seq[A], List[A]].

8

Nếu bạn đang mở để thay thế các addresses trong Company từ Seq đến List, bạn có thể sử dụng "Scrap Your Boilerplate" của bạn từ 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"))))) 

Bạn có thể tạo chức năng đa hình vốn viết hoa tên là Street nếu tên bắt đầu bằng "b".

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

Mà bạn có thể sử dụng như:

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

Cảm ơn rất nhiều !!! Điều đó thật tuyệt. Cuối cùng tôi đã có cơ hội để biết sức mạnh vô hình là thế nào! – Freewind

+3

Câu trả lời hay, nhưng hãy xem tôi để biết cảnh báo (điều này sẽ biến đổi _any_ tên đường trong cấu trúc dữ liệu). –

2

Hãy xem quicklens

Bạn có thể làm điều đó như thế này

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 
    } 
    } 
} 
Các vấn đề liên quan