วิธีโคลนอินสแตนซ์คลาสของเคสและเปลี่ยนเพียงหนึ่งฟิลด์ใน Scala ได้อย่างไร


208

สมมติว่าฉันมีคลาสเคสที่แสดงถึงตัวบุคคลผู้ใช้บนเครือข่ายโซเชียลต่างๆ อินสแตนซ์ของคลาสนั้นไม่เปลี่ยนแปลงอย่างสมบูรณ์และถูกจัดเก็บไว้ในคอลเลกชันที่ไม่เปลี่ยนรูปแบบซึ่งจะได้รับการแก้ไขในที่สุดโดยนักแสดง Akka

ตอนนี้ฉันมีคลาสเคสที่มีหลายฟิลด์และฉันได้รับข้อความแจ้งว่าต้องอัปเดตหนึ่งในฟิลด์ดังนี้:

case class Persona(serviceName  : String,
                   serviceId    : String,
                   sentMessages : Set[String])

// Somewhere deep in an actor
val newPersona = Persona(existingPersona.serviceName,
                         existingPersona.serviceId,
                         existingPersona.sentMessages + newMessage)

สังเกตุฉันต้องระบุทุกฟิลด์แม้ว่าจะมีการเปลี่ยนแปลงเพียงครั้งเดียว มีวิธีการโคลน Persona ที่มีอยู่และแทนที่เพียงหนึ่งฟิลด์โดยไม่ระบุฟิลด์ทั้งหมดที่ไม่เปลี่ยนแปลงหรือไม่ ฉันสามารถเขียนเป็นคุณลักษณะและใช้สำหรับคลาสเคสทั้งหมดของฉันได้หรือไม่?

ถ้า Persona เป็นอินสแตนซ์ที่เหมือนกับแผนที่มันจะง่ายมาก

คำตอบ:


324

case classมาพร้อมกับcopyวิธีการที่เฉพาะเจาะจงกับการใช้งานนี้

val newPersona = existingPersona.copy(sentMessages = 
                   existingPersona.sentMessages + newMessage)

5
เอกสารนั้นอยู่ที่ไหน ฉันไม่พบการอ้างอิงเพื่อคัดลอกในจุด "ชัดเจน" scala-lang.org/api/current/index.htmlเป็นต้น
François Beausoleil

6
มันเป็นคุณสมบัติของภาษาคุณสามารถค้นหาได้ในข้อมูลจำเพาะของ Scala: scala-lang.org/docu/files/ScalaReference.pdf §5.3.2 มันไม่ได้อยู่ใน API เพราะมันไม่ได้เป็นส่วนหนึ่งของ API;)
นิโคลัส

1
ฉันตั้งใจจะให้ ScalaDoc แสดงวิธีการคัดลอกเมื่อมีอยู่นั่นไม่ใช่สิ่งที่คุณต้องการใช่ไหม
soc

4
มันจะดี แต่ที่นี่ปัญหาของFrançois (ถ้าฉันขวา) คือการที่เขาไม่ได้รู้ว่าเขาจะมีวิธีการที่ถ้าเขาประกาศcopy case class
นิโคลัส

2
@JanathanNeufeld คุณจะสร้างเพื่อนที่ไม่ดีมากมายในค่าย fp ที่บริสุทธิ์ด้วยความรู้สึกนั้น ฉันมักจะเห็นด้วยกับคุณ
javadba

46

ตั้งแต่ 2.8 คลาสเคส Scala มีcopyวิธีที่ใช้ประโยชน์จาก params ที่ระบุชื่อ / ค่าเริ่มต้นเพื่อใช้เวทย์มนตร์:

val newPersona =
  existingPersona.copy(sentMessages = existing.sentMessages + newMessage)

คุณยังสามารถสร้างวิธีการPersonaเพื่อให้การใช้งานง่ายขึ้น:

case class Persona(
  svcName  : String,
  svcId    : String,
  sentMsgs : Set[String]
) {
  def plusMsg(msg: String) = this.copy(sentMsgs = this.sentMsgs + msg)
}

แล้วก็

val newPersona = existingPersona plusMsg newMsg


0

พิจารณาใช้lensในShapelessห้องสมุด:

import shapeless.lens

case class Persona(serviceName  : String,
                   serviceId    : String,
                   sentMessages : Set[String])
// define the lens
val messageLens = lens[Persona] >> 'sentMessages 

val existingPersona = Persona("store", "apple", Set("iPhone"))

// When you need the new copy, by setting the value,
val newPersona1 = messageLens.set(existingPersona)(Set.empty)
// or by other operation based on current value.
val newPersona2 = messageLens.modify(existingPersona)(_ + "iPad")

// Results:
// newPersona1: Persona(store,apple,Set())
// newPersona2: Persona(store,apple,Set(iPhone, iPad))

ยิ่งไปกว่านั้นในกรณีที่คุณมีคลาสเคสที่ซ้อนกันวิธีการgetterและsetterวิธีการอาจจะค่อนข้างน่าเบื่อในการเขียน มันจะเป็นโอกาสที่ดีที่จะทำให้ง่ายขึ้นโดยใช้ไลบรารี่เลนส์

โปรดอ้างอิงถึง:


0

ฉันไม่ต้องการรวมห้องสมุดขนาดใหญ่เพื่อทำเลนส์คอมเพล็กซ์ที่ให้คุณตั้งค่าได้ลึกลงไปในคลาสเคสที่ซ้อนกัน ปรากฎว่าเป็นโค้ดเพียงไม่กี่บรรทัดในไลบรารี scalaz:

  /** http://stackoverflow.com/a/5597750/329496 */
  case class Lens[A, B](get: A => B, set: (A, B) => A) extends ((A) => B) with Immutable {
    def apply(whole: A): B = get(whole)

    def mod(a: A, f: B => B) = set(a, f(this (a)))

    def compose[C](that: Lens[C, A]) = Lens[C, B](
      c => this(that(c)),
      (c, b) => that.mod(c, set(_, b))
    )

    def andThen[C](that: Lens[B, C]) = that compose this
  }

จากนั้นคุณสามารถสร้างเลนส์ที่ตั้งค่าซ้อนกันอย่างล้ำลึกได้ง่ายกว่าการใช้คุณสมบัติการถ่ายเอกสารในตัว นี่คือลิงค์ไปสู่ชุดใหญ่ถ้าเลนส์คอมเพล็กซ์ที่ห้องสมุดของฉันใช้เพื่อตั้งค่าซ้อนกันอย่างหนัก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.