Common members of union types

It sounds like a useful feature to have common members of union types inferred by the compiler. Something like

def processExpirationDate(product: CreditCard | Account): Unit =
  println(product.expirationDate)

without a need for typeclasses if it just so happens that expirationDate is present in both and has the same type.

What are the reasons Scala 3 cannot do that? Technical? Or maybe such practice would be considered harmful?

1 Like

Yes, I’d like to have this in enums too. Of course if it’s a bad idea then fine :smile:

1 Like

For enums, like this (see the 3rd version):

For union types, this is by design. It was discussed a few times, including all the way up into a SIP committee meeting, but we don’t want it. There are “philosophical” and practical reasons for which it is undesirable. For the former, it doesn’t fit well with the nominal philosophy of Scala, in which a method is not just a signature but also a contract. For the latter, it results in surprisingly inefficient code if we need to compile it away, and that’s when we can even compile it away.

5 Likes

Scala infers and synthesizes quite a lot of cruft.

It could synthesize the required HasExpirationDate trait for CreditCard and Account, assuming everything compiled together.

A contract is an agreement between two parties. What is called a contract in API design is merely fiat, an offer you can’t refuse.

This one-sided state of affairs is normalized by programmers who are accustomed to top-down decision making.

Currently, the only tool wielded by the consumers is to organize a strike or, in another metaphor, a boycott.

The other metaphor sanctioned by computing culture is opting into cookies. “If you don’t like our offering, you can refuse and everything will break.” That is also not a contract.

Too bad, I thought enum cases worked just like case classes. So I expected enum Spam2(x: Int): to have public getters… :sob:

So I guess it’s a bad design indicator that, if an enum is necessitating implementing some getters, it probably shouldn’t be an enum? (I run into this situation all the time on Advent of Code puzzles.) I should fall back to sealed trait + case class... instead?

Enum cases are like case classes. The enum itself is a non-case abstract class. So the arguments to the cases yield accessors, but not the arguments to the enum.

Is the solution I proposed not satisfactory?

It’s not satisfactory (of course it works fine, no problem there) but I understand now. :smiley: I would have expected my Spam2 to work like your Spam3. It wasn’t clear to me that it’s an abstract class. I can see that there are good reasons for that.

This is equivalent to your Spam3 right?

enum Spam(val x: Int): 
  ...

Anyway, I’ll just have to design differently… :smile:

No, it’s not equivalent. That declares a concrete member that must be provided as constructor argument. My solution defines an abstract member that is automatically implemented by the accessors of the individual cases. That makes for less boilerplate.

2 Likes

For reference, here is a previous thread on this on contributors:

1 Like

And here are my slides from the SIP meeting back then: