Proposal: deep copy syntax support on language level

Tysvm for the pointer to QuickLens. It really seems to nail the balance between being a Scala idiomatic and simple to use API for Lenses while also maintaining high performance and covering the vast majority of the normal day-to-day use cases.

2 Likes

Scala already has auto-generated “copy” method for case-classes and already emphasizes using immutable values. So, it’s not about some specific area, but about short syntax for something, that already exists.

And if we talk about history lessons… C++ had map-like methods in Boost in time of 90-s. But almost nobody used them, because of syntax. Java had anonymous classes and closures for them 20 years ago, so, it was possible to make map-like methods, but almost nobody did, because of syntax. If someone needs to write too many and it will be hard to write and read, possibility of writing it will be low, if there is shorter and easier alternative. Even, if longer one is much better hypothetically.

Now I see, in real projects and even in libraries people use immutable values not too often, if they need a hierarchy. Just because of syntax.

If language allows to write something to library for future using in short and readable way, it must be written to library. But the subject is not the case: it is just impossible now, even with new macros. But on compiler level it is very easy to translate syntax, like I suggested, to series of “copy” method calls. And it will open the door to using immutables in a way, such as easy, as using mutables.

Just try Monocle or QuickLens not for web-mini-services, but for apps with GUI, games, document editing or something like, and you feel what I feel.

2 Likes

@lex-kravetski How would You encode in Your syntax a modify like this (from quicklens)?

case class Tree[+T](e: T, children: Seq[Tree[T]])

// increment e of every branch on depth 2 (counting from 0)
tree.modify(_.children.each.children.each.e).using(_ + 1)
1 Like

My problem with this is that it breaks the substitution principle.

Consider:

val x = person.address.street.{name = name.toUpperCase}

vs

val a = person.address
val x = a.street.{name = name.toUpperCase}

By substitution, we would like these two syntaxes to be the same, however, your suggestion is that they not be.

I think this makes the syntax error prone and harder to teach.

Having a syntax for lens could be nice, but I think respecting the substitution principle even in syntax is something we should always strive for.

6 Likes

I think, it should be

tree.{
  children = _.map {
    _.{
      children = _.map {
         _.{e = _ + 1}
      } 
    }
  } 
}

(I use RubenVerg syntax-sugar suggestion here).

But it is straight solution, that’s using standard library methods only. With macros and type-classes someone can wrap tree deep iterations in shorter form like

tree.mapLevel(2)( _.{e = _ + 1} )

As I answered in https://contributors.scala-lang.org/t/proposal-deep-copy-syntax-support-on-language-level/5061/19?u=lex-kravetski, with corrected syntax, these two cases should be the same: in both of them, it will be deep copy of “street” field value.

quicklens still can modify existing values using… using(_) :slight_smile:

person
  .modify(_.address.street.name).using(_ + " (temporary)")
  .modify(_.address.street.length).using(_ + 1)

you can still reuse part of path but it is kind of uglier:

person.modify(_.address.street).using( 
    _.modify(_.name).using(_ + " (temporary)")
     .modify(_.length).using(_ + 1)
)

in your proposition you need to repeat whole path or part of it (depending on specs).

person.{
  address.street.name = address.street.name + " (temporary)", 
  address.street.length = person.address.street.length + 1
}

person.{
  address.street.{
    //I guess here should be available all fields of `person.*` and `streat.*`
    name = name + " (temporary)", //person.address.street.name -> name
    length = length + 1
  }
}

In my propositions, it mimics “copy” method of case-classes, so you should be able to write

person.{address.street = address.street.{
  name = name + " (temporary)", 
  length = length + 1
}}

Probably, it can be shorter with underscore

person.{address.street = _.{
  name = _ + " (temporary)", 
  length = _ + 1
}}

But I like your shorter example, if it will not give any contradictions.

person.{
  address.street.{ 
  …