【问题标题】:How to modify this nested case classes with "Seq" fields?如何使用“Seq”字段修改此嵌套案例类?
【发布时间】:2016-01-20 13:34:14
【问题描述】:

一些嵌套的案例类和字段addressesSeq[Address]

// ... 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. 不要丢失其他现有字段

【问题讨论】:

    标签: scala case-class lenses


    【解决方案1】:

    正如 Peter Neyens 指出的,Shapeless 的 SYB 在这里工作得非常好,但它会修改树中的 all Street 值,这可能并不总是你想要的。如果您需要对路径进行更多控制,Monocle 可以提供帮助:

    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 在编辑中指出的那样,您可以通过创建一个镜头一直到街道名称的第一个字符来使其更加简洁(但不那么笼统):

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

    请注意,与 Shapeless 答案一样,您需要将 Employee 定义更改为使用 List 而不是 Seq,或者如果您不想更改模型,则可以构建该转换使用Iso[Seq[A], List[A]] 进入Lens

    【讨论】:

      【解决方案2】:

      如果您愿意将 Company 中的 addressesSeq 替换为 List,您可以使用 shapeless (example) 中的“Scrap Your Boilerplate”。

      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”开头,您可以创建一个将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)))))
      

      【讨论】:

      • 非常感谢!!!这太酷了。终于有机会知道无形有多强大了!
      • 不错的答案,但请参阅我的警告(这将转换数据结构中的 任何 街道名称)。
      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 2016-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-27
        相关资源
        最近更新 更多