如何使用“Seq”字段修改此嵌套案例类?

Posted

技术标签:

【中文标题】如何使用“Seq”字段修改此嵌套案例类?【英文标题】:How to modify this nested case classes with "Seq" fields? 【发布时间】: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】:

正如 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
    
  

【讨论】:

以上是关于如何使用“Seq”字段修改此嵌套案例类?的主要内容,如果未能解决你的问题,请参考以下文章

如何自动将修改应用于Scala中案例类的所有/部分字段?

如何使用 AvroParquetWriter 从 scala 案例类制作镶木地板文件?

如何使用播放框架案例类绑定确认密码字段?

使用 circe 在 Scala 中 JSON 将嵌套字段解码为 Map[String, String]

在编译时验证 Scala 案例类

oracle 数据库code 拼接字段如何转换成文字