নেস্টেড স্ট্রাকচারগুলি আপডেট করার পরিষ্কার উপায়


124

বলুন আমি দু'টি নিম্নলিখিত পেয়েছি case class:

case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)

এবং Personক্লাসের নিম্নলিখিত উদাহরণ :

val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg", 
                                           "Mumbai", 
                                           "Maharashtra", 
                                           411342))

এখন যদি আমি আপডেট করতে চান zipCodeএর rajতারপর আমি কি করতে হবে:

val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))

বাসা বাঁধার আরও স্তর সহ এটি আরও কৃপণ হয়ে ওঠে। এই update-inধরণের নেস্টেড স্ট্রাকচারগুলি আপডেট করার জন্য কি কোনও ক্লিনার (ক্লোজারের মতো কিছু ) রয়েছে?


1
আমি ধরে নিয়েছি আপনি অপরিবর্তনীয়তা রক্ষা করতে চান, অন্যথায়, কেবলমাত্র ব্যক্তির ঠিকানা ঘোষণার সামনে একটি ভেরিটি আটকে দিন।
GClaramunt

8
@ জি-ক্লারামুন্ট: হ্যাঁ, আমি অপরিবর্তনীয়তা রক্ষা করতে চাই।
অনুপস্থিত

উত্তর:


94

জিপার্স

হুটের জিপার একটি অপরিবর্তনীয় ডেটা কাঠামোর সুবিধাজনক ট্রভারসাল এবং 'মিউটেশন' সরবরাহ করে। স্কালাজ Stream( স্কালাজ.জিপার ) এবং Tree( স্ক্যালাজ.ট্রিলক ) এর জন্য জিপার সরবরাহ করে । এটি দেখা যাচ্ছে যে জিপারের কাঠামোটি মূল উপাত্ত কাঠামো থেকে স্বয়ংক্রিয়ভাবে উদ্ভূত হয়, এমন একটি উপায়ে যা বীজগণিতিক প্রকাশের প্রতীকী পার্থক্যের সাথে সাদৃশ্যপূর্ণ।

তবে কীভাবে এটি আপনার স্কালা কেস ক্লাসগুলিতে আপনাকে সহায়তা করে? ভাল, লুকাশ রায়েজ সম্প্রতি স্কেল্যাকের একটি এক্সটেনশান প্রোটোটাইপ করেছে যা টীকাযুক্ত কেস ক্লাসগুলির জন্য স্বয়ংক্রিয়ভাবে জিপার তৈরি করে। আমি এখানে তার উদাহরণ পুনরুত্পাদন:

scala> @zip case class Pacman(lives: Int = 3, superMode: Boolean = false) 
scala> @zip case class Game(state: String = "pause", pacman: Pacman = Pacman()) 
scala> val g = Game() 
g: Game = Game("pause",Pacman(3,false))

// Changing the game state to "run" is simple using the copy method:
scala> val g1 = g.copy(state = "run") 
g1: Game = Game("run",Pacman(3,false))

// However, changing pacman's super mode is much more cumbersome (and it gets worse for deeper structures):
scala> val g2 = g1.copy(pacman = g1.pacman.copy(superMode = true))
g2: Game = Game("run",Pacman(3,true))

// Using the compiler-generated location classes this gets much easier: 
scala> val g3 = g1.loc.pacman.superMode set true
g3: Game = Game("run",Pacman(3,true)

সুতরাং সম্প্রদায়ের স্কেল দলকে বোঝানো দরকার যে এই প্রচেষ্টাটি চালিয়ে যেতে হবে এবং সংকলকটিতে সংহত করা উচিত।

ঘটনাচক্রে, লুকাশ সম্প্রতি একটি ডিএসএল-এর মাধ্যমে ব্যবহারকারী প্রোগ্রামেবল প্যাকম্যানের একটি সংস্করণ প্রকাশ করেছে । তিনি পরিবর্তিত সংকলকটি ব্যবহার করেছেন বলে মনে হচ্ছে না, যদিও আমি কোনও @zipটিকা দেখতে পাচ্ছি না ।

গাছ পুনর্লিখন

অন্যান্য পরিস্থিতিতে, আপনি কিছু কৌশল অনুসারে পুরো ডাটা স্ট্রাকচার জুড়ে কিছু রূপান্তর প্রয়োগ করতে পছন্দ করতে পারেন (টপ-ডাউন, নীচে-আপ), এবং কাঠামোর কোনও পর্যায়ে মানের সাথে মেলে এমন নিয়মের ভিত্তিতে। শাস্ত্রীয় উদাহরণটি কোনও ভাষার জন্য একটি এএসটি রূপান্তর করছে, সম্ভবত মূল্যায়ন, সরলকরণ বা তথ্য সংগ্রহের জন্য। Kiama সমর্থন rewriting , র উদাহরণগুলি দেখুন RewriterTests , এবং এই ঘড়ি ভিডিও । আপনার ক্ষুধা ঘটাতে এখানে একটি স্নিপেট রয়েছে:

// Test expression
val e = Mul (Num (1), Add (Sub (Var ("hello"), Num (2)), Var ("harold")))

// Increment every double
val incint = everywheretd (rule { case d : Double => d + 1 })
val r1 = Mul (Num (2), Add (Sub (Var ("hello"), Num (3)), Var ("harold")))
expect (r1) (rewrite (incint) (e))

নোট করুন কিয়ামা এটি অর্জনের জন্য টাইপ সিস্টেমের বাইরে পদক্ষেপ নেয়।


2
প্রতিশ্রুতি খুঁজছেন যারা জন্য। এটি এখানে: github.com/soundrabbit/scala/commit/… (আমার মনে হয় ..)
IttayD

15
আরে লেন্স গুলো কোথায়?
ড্যানিয়েল সি সোব্রাল

আমি কেবল এই সমস্যার মুখোমুখি হয়েছি এবং @ জিপ ধারণাটি সত্যিই দুর্দান্ত লাগছে, সম্ভবত এটি এতদূর নেওয়া উচিত যে সমস্ত কেস ক্লাস রয়েছে? কেন এটি বাস্তবায়িত হয় না? লেন্সগুলি দুর্দান্ত তবে বড় এবং অনেকগুলি ক্লাস / কেস ক্লাস সহ এটি কেবল বয়লারপ্লেট হয় যদি আপনি কেবল একটি সেটার চান এবং কোনও ইনক্রিমেন্টারের মতো অভিনব কিছু না।
জোহান এস

186

মজার বিষয় যে কোনও লেন্স যুক্ত করেনি, যেহেতু তারা এই ধরণের স্টাফের জন্য তৈরি হয়েছিল। সুতরাং, এখানে এটিতে একটি সিএস ব্যাকগ্রাউন্ড পেপার রয়েছে, এখানে এমন একটি ব্লগ যা স্কালায় লেন্সগুলি ব্যবহারের জন্য সংক্ষেপে স্পর্শ করে, এখানে স্কালাজের জন্য একটি লেন্স প্রয়োগকরণ এবং এখানে এটি ব্যবহার করার জন্য কিছু কোড রয়েছে, যা আপনার প্রশ্নের মতো আশ্চর্যরকম দেখাচ্ছে। এবং, বয়লার প্লেটটি কাটাতে, এখানে একটি প্লাগইন রয়েছে যা কেস ক্লাসগুলির জন্য স্কালাজ লেন্স তৈরি করে।

বোনাস পয়েন্টগুলির জন্য, এখানে আরও একটি এসও প্রশ্ন যা লেন্সগুলিতে স্পর্শ করে এবং টনি মরিসের একটি কাগজ

লেন্স সম্পর্কে বড় কথা হ'ল তারা কম্পোজেবল। সুতরাং এগুলি প্রথমে কিছুটা কষ্টকর, তবে আপনি যত বেশি এগুলি ব্যবহার করেন সেগুলি তারা স্থির করতে থাকে। এছাড়াও, তারা পরীক্ষার জন্য দুর্দান্ত, যেহেতু আপনাকে কেবল পৃথক লেন্স পরীক্ষা করতে হবে, এবং তাদের রচনাটি মঞ্জুর করতে পারে।

সুতরাং, এই উত্তরের শেষে প্রদত্ত একটি বাস্তবায়নের ভিত্তিতে আপনি লেন্স দিয়ে কীভাবে করবেন তা এখানে রয়েছে। প্রথমে, কোনও ঠিকানার মধ্যে একটি জিপ কোড এবং কোনও ব্যক্তির একটি ঠিকানা পরিবর্তন করার জন্য লেন্সগুলি ঘোষণা করুন:

val addressZipCodeLens = Lens(
    get = (_: Address).zipCode,
    set = (addr: Address, zipCode: Int) => addr.copy(zipCode = zipCode))

val personAddressLens = Lens(
    get = (_: Person).address, 
    set = (p: Person, addr: Address) => p.copy(address = addr))

এখন, তাদের এমন একটি লেন্স পাওয়ার জন্য রচনা করুন যা কোনও ব্যক্তির মধ্যে জিপকোড পরিবর্তন করে:

val personZipCodeLens = personAddressLens andThen addressZipCodeLens

অবশেষে, রাজ পরিবর্তন করতে সেই লেন্স ব্যবহার করুন:

val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)

বা, কিছু সিনট্যাকটিক চিনি ব্যবহার করে:

val updatedRaj = personZipCodeLens.set(raj, personZipCodeLens(raj) + 1)

অথবা এমনকি:

val updatedRaj = personZipCodeLens.mod(raj, zip => zip + 1)

এই উদাহরণের জন্য ব্যবহৃত স্ক্যালাজ থেকে নেওয়া সহজ বাস্তবায়ন এখানে:

case class Lens[A,B](get: A => B, set: (A,B) => A) extends Function1[A,B] with Immutable {
  def apply(whole: A): B   = get(whole)
  def updated(whole: A, part: B): A = set(whole, part) // like on immutable maps
  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
}

1
জেরল্ফ সিটিজের লেন্স প্লাগইনের বিবরণ দিয়ে আপনি এই উত্তরটি আপডেট করতে চাইতে পারেন।
মিসিংফ্যাক্টর

নিবন্ধন করুন লিঙ্ক করবেন? আমি এই ধরনের প্লাগইন সম্পর্কে অবগত ছিলাম না।
ড্যানিয়েল সি সোব্রাল

1
কোড personZipCodeLens.set(raj, personZipCodeLens.get(raj) + 1)হিসাবে একইpersonZipCodeLens mod (raj, _ + 1)
Ron

@ লোন modলেন্সের জন্য আদিম নয়।
ড্যানিয়েল সি। সোব্রাল

টনি মরিস এই বিষয়ে একটি দুর্দান্ত কাগজ লিখেছেন । আমি মনে করি আপনার উত্তরে এটি লিঙ্ক করা উচিত।
মিসিংফ্যাক্টর

11

লেন্স ব্যবহারের জন্য দরকারী সরঞ্জাম:

কেবল এটি যুক্ত করতে চাই যে স্কেলা ২.১০ ম্যাক্রো ভিত্তিক ম্যাক্রোকোজম এবং রিলিট প্রকল্পগুলি ডায়নামিক লেন্স ক্রিয়েশন সরবরাহ করে।


রিলিট ব্যবহার:

case class Email(user: String, domain: String)
case class Contact(email: Email, web: String)
case class Person(name: String, contact: Contact)

val person = Person(
  name = "Aki Saarinen",
  contact = Contact(
    email = Email("aki", "akisaarinen.fi"),
    web   = "http://akisaarinen.fi"
  )
)

scala> Lenser[Person].contact.email.user.set(person, "john")
res1: Person = Person(Aki Saarinen,Contact(Email(john,akisaarinen.fi),http://akisaarinen.fi))

ম্যাক্রোকোজম ব্যবহার:

এটি বর্তমান সংকলন রানে সংজ্ঞায়িত কেস ক্লাসগুলির জন্যও কাজ করে।

case class Person(name: String, age: Int)

val p = Person("brett", 21)

scala> lens[Person].name._1(p)
res1: String = brett

scala> lens[Person].name._2(p, "bill")
res2: Person = Person(bill,21)

scala> lens[Person].namexx(()) // Compilation error

আপনি সম্ভবত রিলিটকে মিস করেছেন যা আরও ভাল। :-) github.com/akisaarinen/rillit
অনুপস্থিত ফ্যাক্টর

ভাল, এটি পরীক্ষা করবে
সেবাস্তিয়ান লরবার

1
বিটিডব্লিউ আমি রিলিটকে অন্তর্ভুক্ত করার জন্য আমার উত্তরটি সম্পাদনা করেছি তবে রিলিট কেন আরও ভাল তা আমি সত্যিই বুঝতে পারছি না, তারা প্রথম দৃষ্টিভঙ্গিতে একই শব্দভাণ্ডারে একই কার্যকারিতা সরবরাহ করে বলে মনে হচ্ছে @ মিসিংফ্যাক্টর
সেবাস্তিয়ান লরবার

@ শেবাস্টিয়ানলবার মজাদার ঘটনা: রিলিট ফিনিশ এবং মানে লেন্স :)
কাই সেলগ্রেন

ম্যাক্রোকোজম এবং রিলিট উভয়ই গত 4 বছরে আপডেট হয়নি বলে মনে হচ্ছে।
এরিক ভান ওস্টেন

9

আমি যে স্ক্যালার লাইব্রেরিটিতে সর্বোত্তম সিনট্যাক্স এবং সেরা কার্যকারিতা এবং একটি লাইব্রেরি এখানে উল্লেখ করা হয়নি এটি একচেটিয়া যা আমার জন্য সত্যই ভাল ছিল তা সন্ধান করছি। একটি উদাহরণ অনুসরণ করে:

import monocle.Macro._
import monocle.syntax._

case class A(s: String)
case class B(a: A)

val aLens = mkLens[B, A]("a")
val sLens = aLens |-> mkLens[A, String]("s")

//Usage
val b = B(A("hi"))
val newB = b |-> sLens set("goodbye") // gives B(A("goodbye"))

এগুলি খুব সুন্দর এবং লেন্সগুলি একত্রিত করার বিভিন্ন উপায় রয়েছে। উদাহরণস্বরূপ স্কালাজ অনেকগুলি বয়লারপ্লেট দাবি করে এবং এটি দ্রুত সংকলন করে এবং দুর্দান্ত চালায়।

আপনার প্রকল্পে এগুলি ব্যবহার করতে কেবল এটি আপনার নির্ভরতার সাথে যুক্ত করুন:

resolvers ++= Seq(
  "Sonatype OSS Releases"  at "http://oss.sonatype.org/content/repositories/releases/",
  "Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
)

val scalaVersion   = "2.11.0" // or "2.10.4"
val libraryVersion = "0.4.0"  // or "0.5-SNAPSHOT"

libraryDependencies ++= Seq(
  "com.github.julien-truffaut"  %%  "monocle-core"    % libraryVersion,
  "com.github.julien-truffaut"  %%  "monocle-generic" % libraryVersion,
  "com.github.julien-truffaut"  %%  "monocle-macro"   % libraryVersion,       // since 0.4.0
  "com.github.julien-truffaut"  %%  "monocle-law"     % libraryVersion % test // since 0.4.0
)

7

নির্লজ্জ কৌশলটি করে:

"com.chuusai" % "shapeless_2.11" % "2.0.0"

সঙ্গে:

case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)

object LensSpec {
      import shapeless._
      val zipLens = lens[Person] >> 'address >> 'zipCode  
      val surnameLens = lens[Person] >> 'firstName
      val surnameZipLens = surnameLens ~ zipLens
}

class LensSpec extends WordSpecLike with Matchers {
  import LensSpec._
  "Shapless Lens" should {
    "do the trick" in {

      // given some values to recreate
      val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
        "Mumbai",
        "Maharashtra",
        411342))
      val updatedRaj = raj.copy(address = raj.address.copy(zipCode = raj.address.zipCode + 1))

      // when we use a lens
      val lensUpdatedRaj = zipLens.set(raj)(raj.address.zipCode + 1)

      // then it matches the explicit copy
      assert(lensUpdatedRaj == updatedRaj)
    }

    "better yet chain them together as a template of values to set" in {

      // given some values to recreate
      val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg",
        "Mumbai",
        "Maharashtra",
        411342))

      val updatedRaj = raj.copy(firstName="Rajendra", address = raj.address.copy(zipCode = raj.address.zipCode + 1))

      // when we use a compound lens
      val lensUpdatedRaj = surnameZipLens.set(raj)("Rajendra", raj.address.zipCode+1)

      // then it matches the explicit copy
      assert(lensUpdatedRaj == updatedRaj)
    }
  }
}

মনে রাখবেন যে এখানে কিছু অন্যান্য উত্তর আপনাকে প্রদত্ত কাঠামোর গভীরে যেতে লেন্সগুলি রচনা করতে দেয় (এই লাইব্রেরি / ম্যাক্রোগুলি) আপনাকে দুটি অপ্রাসঙ্গিক লেন্স সংযোজন করতে দেয় যাতে আপনি লেন্স তৈরি করতে পারেন যা স্বেচ্ছাচারিত অবস্থানগুলিতে একটি নির্বিচার সংখ্যক পরামিতি সেট করে sets আপনার কাঠামোর মধ্যে জটিল ডেটা স্ট্রাকচারের জন্য অতিরিক্ত রচনাটি খুব সহায়ক।


নোট করুন যে আমি শেষ পর্যন্ত Lensড্যানিয়েল সি সোব্রালের উত্তরে কোডটি ব্যবহার করে শেষ করেছি এবং তাই বাহ্যিক নির্ভরতা যুক্ত করা এড়ানো হয়েছে।
simbo1905

7

তাদের কম্পোজেবল প্রকৃতির কারণে, লেন্সগুলি ভারী নেস্টেড স্ট্রাকচারগুলির সমস্যার খুব সুন্দর সমাধান সরবরাহ করে। তবে নীচে স্তরের নেস্টিংয়ের সাথে আমার মাঝে মাঝে লেন্সগুলি কিছুটা বেশি হয়ে যায় বলে মনে হয় এবং নেস্টেড আপডেটের সাথে খুব কম জায়গা থাকলে আমি পুরো লেন্সগুলির পদ্ধতির পরিচয় দিতে চাই না। সম্পূর্ণতার জন্য, এখানে এই ক্ষেত্রে একটি খুব সহজ / বাস্তববাদী সমাধান:

আমি যা করি তা হ'ল modify...শীর্ষ স্তরের কাঠামোয় কয়েকটি সহায়ক ফাংশন লিখি , যা কুৎসিত নেস্টেড অনুলিপিটি নিয়ে কাজ করে। এই ক্ষেত্রে:

case class Person(firstName: String, lastName: String, address: Address) {
  def modifyZipCode(modifier: Int => Int) = 
    this.copy(address = address.copy(zipCode = modifier(address.zipCode)))
}

আমার মূল লক্ষ্য (ক্লায়েন্ট পক্ষের আপডেটকে সহজীকরণ করা) অর্জন করা হয়েছে:

val updatedRaj = raj.modifyZipCode(_ => 41).modifyZipCode(_ + 1)

পরিমার্জিত সাহায্যকারীদের সম্পূর্ণ সেট তৈরি করা স্পষ্টতই বিরক্তিকর। তবে অভ্যন্তরীণ স্টাফগুলির জন্য প্রায়শই প্রথম বার আপনি কোনও নির্দিষ্ট নেস্টেড ক্ষেত্রটি পরিবর্তন করার চেষ্টা করার জন্য এগুলি তৈরি করা ঠিক হয় okay


4

সম্ভবত কুইক লেন্স আপনার প্রশ্নের সাথে আরও ভাল মেলে। কুইক লেন্স আইডিই বন্ধুত্বপূর্ণ এক্সপ্রেশনটিকে এমন কোনও কিছুতে রূপান্তর করতে ম্যাক্রোর ব্যবহার করে যা মূল অনুলিপি স্টেটমেন্টের কাছে।

দুটি উদাহরণসত্তার ক্লাস দেওয়া:

case class Address(street: String, city: String, state: String, zipCode: Int)
case class Person(firstName: String, lastName: String, address: Address)

এবং ব্যক্তি শ্রেণির উদাহরণ:

val raj = Person("Raj", "Shekhar", Address("M Gandhi Marg", 
                                           "Mumbai", 
                                           "Maharashtra", 
                                           411342))

আপনি এর সাথে রাজের জিপকোড আপডেট করতে পারেন:

import com.softwaremill.quicklens._
val updatedRaj = raj.modify(_.address.zipCode).using(_ + 1)
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.