Scala shortcomings for modeling ASTs

When modeling ASTs, typically most nodes will have their shape defined as case class with arguments, but in many asts, you typically have secondary properties that you don’t want to have them be a part of the deconstructors, they are optional in the constructors, they probably don’t affect hashCode/equals, you may or not want them in the toString, but crucially, you do want in the copy() method, since nodes are immutable.
Scala’s case class syntax is very inadequate for this, and from looking at SBT, scalameta, and the scala compiler, they all end up modelling their asts using either macro annotations, or compiler plugins (*).

What happened to the many improvements requests around this topic?

(*) I actually haven’t checked if they are using compiler plugins, but I imagine they must be since starting with scala 3, annotation macros can no longer change the public api of a type (so no introduction of methods or fields).

Only first arguments list of primary constructor is part of the generated equals, hashCode, toString methods and used in extractor object. If you don’t want some properties to be part of this simply put them in second arguments list, here’s an example

case class Model(version: Int, name: String)(likeCount: Int) 

val m1 = Model(1, "first")(0)
val m2 = m1.copy()(likeCount = 5)

assert(m1 ne m2)
assert(m1 == m2)
assert(m1.## == m2.##)
assert(m1.toString == m2.toString())

m1 match 
  case Model(version, _) => version 

// Named tuples syntax
m2 match 
  case Model(name = "first") => "that's a first one"
  case _ => sys.error("That's something else")

val Model(_, m1Name) = m1 
assert(m1Name == "first")

Link to Scastie with the same example

1 Like

counter example:

val m3 = m1.copy()()

and you get a compilation error. A copy method where there are mandatory arguments that you must manually copy over defeats the purpose of the copy method, and this is particularly painful because these are the secondary (in importance) parameters.

Besides there are the other arguments, where whether I want or not the optional attributes in the toString is AST dependent (and I want to say that for equals/hashcode it’s fine as it currently is for case classes but I don’t think I can assert this)

Can you give an example of what you’re trying to do? I having trouble envisioning the use case, and it would probably help clarify this discussion.

It’s a bit annoying, but you can write your own copy method that does what you want, replacing the compiler’s auto-generated one. I have done this in my own code.

2 Likes

Yes, writing your own copy method works. The compiler-generated method cannot possibly do this, since parameters of case classes following the first clause are not val’s by default, so they are not retained in the class for copying. This is by design. You often do not want these secondary arguments to be retained as fields in the class.

1 Like

@rcano Sounds like you have a rich domain object on the one hand, but only want a subset on the other.

Try Chimney documentation - that might help you.

Alternatively, write a custom extractor unapply that retrieves just the properties that you want.

(EDIT: or put all the optional properties in their own case class and have a single attribute of that type up in your big domain class; then you can ignore all the optional bits with a single underscore placeholder in your pattern matches. Or put them loosely typed into a map, expando-style.)

1 Like

Thanks for the responses; I believe you have missed the point of the post. Yes, there are lots of workarounds to this (including just not using scala), they are all very tedious and boilerplate-full, take a lot of time for any non trivial AST, and ultimately stunt your work in annoying ways because they are also a pain to refactor.

The point of the post was the question posted at the end, after the contextual introduction:

What happened to the many improvements requests around this topic?

I remember seeing many over the years, such as splitting case class features into traits, macro annotations, maybe others (I briefly read something about inline traits?)