Update multiple fields in a single copy operation?

904 views
Skip to first unread message

Ben Hutchison

unread,
Jan 11, 2015, 6:09:21 PM1/11/15
to scala-...@googlegroups.com
Hi all,

I have a situation where I want to update 2 fields of an record as part of the same operation (see below). Currently, I can achieve this by composing 2 lenses together, but it seems that this copies an unnecessary intermediate object. Ideally, I would like to make a single copy of the containing object, containing both changes. Is there a neat way to express this?

-Ben

case class Board(itemLocations: Map[Item#Id, Location],
                 itemAtLocation: Map[Location#Id, Option[Item]]) {

  val _itemLocations = lenser(_.itemLocations)
  val _itemAtLocation = lenser(_.itemAtLocation)

  def place(m: Item, l: Location): Board = {
    require(!itemLocations.contains(m.id))
    require(!itemAtLocation(l.id).contains(m))

    _itemLocations.modify(_.updated(m.id, l)).compose(
    _itemAtLocation.modify(_.updated(l.id, Some(m))))(this)
  }
}

Ilan Godik

unread,
Jan 12, 2015, 1:56:33 AM1/12/15
to scala-...@googlegroups.com
You can create an Iso[Board,(Map[Item#Id, Location],Map[Location#Id, Option[Item]])] if you are really concered with the intermediate object, to do it in one go.
Is it really a significant overhead for you? Memory-wise, you aren't copying the maps anyway.

בתאריך יום שני, 12 בינואר 2015 בשעה 01:09:21 UTC+2, מאת Ben Hutchison:

Julien Truffaut

unread,
Jan 12, 2015, 4:09:43 AM1/12/15
to scala-...@googlegroups.com
Hi Ben,

In general, when you want to update several fields at the same times, you use Traversal. However, they require the target of the otpic to be of the same type e.g. you update 2 Int fields or 5 String fields but you cannot update an Int and a String. So in your case, Traversal are not suitable. I think the composition of lenses are completely fine, creating an intermediary Board object has almost no performance cost (as far as I know).

Just a few advice:
  • lenses can be seen as static field, i.e. it is more efficient to define them once and for all in the companion object of your case class
  • you can use at or index - 2 generic optics - to zoom into your Map but I am not sure it will make the code clearer. For example: (_itemLocations composeLens at(m.id)).set(Maybe.just(l)) compose (_itemAtLocation composeLens at(l.id).set(Maybe.just(Some(m))))(this)
Cheers

Ben Hutchison

unread,
Jan 15, 2015, 11:52:27 PM1/15/15
to scala-...@googlegroups.com
Thanks Julien and Ilan for your help.

As you say, the object overhead is small, and tuple creation is a new object anyway. If/when some future JVM supports tuple value types, multi-set lenses might be valuable for some hot code paths.

-Ben
Reply all
Reply to author
Forward
0 new messages