Allow renames of givens generated by 'derives' clauses

Currently whenever we use a derives clause to generate a typeclass instance its name in the companion object is generated as ‘derived$NameOfTheTypeclassType’ eg. for a typeclass like Show it’d be derived$Show.

I propose to extend the syntax of ‘derives’ with an optional ‘as name’ clause, for example:

trait Show[A]

object Show {
  def derived[A]: Show[A] = ???
}

case class Person(name: String) derives Show as show

Person.show // I can now access 'show' by using its name as opposed to derived$Show

Ok, but why?

I’ve found myself implementing typeclasses that function as isomorphism between types and that provide methods to convert between those types (which I’d like to expose as constructors on the companion object of the type that derives a given typeclass), let’s take a typeclass called YesNoEnum that captures the isomorphism between enums with cases of Yes and No and Booleans:

trait YesNoEnum[A] {
  extension (self: A) def toBoolean: Boolean

  def apply(bool: Boolean): A
}

object YesNoEnum {
  inline def derived[A <: scala.reflect.Enum](using
    A: Mirror.SumOf[A],
    ev1: A.MirroredElemLabels <:< ("Yes", "No")
  ): YesNoEnum[A] = ???
}

…and an example Yes/No enum that derives our typeclass:

enum Decision derives YesNoEnum {
 case Yes, No
}

So, currently to get my desired behavior (of exposing the apply method from YesNoEnum in the companion) I’d have to do this:

Current state of the art

object Decision {
  export derived$YesNoEnum.{apply as fromBoolean}
}

Which, to be honest, is not that bad! But just seeing the $ in the generated name is enough to make me think this is compiler guts spilling over to my code (+ there’s a weird issue with Metals where the entirety of derived$YesNoEnum doesn’t get autocompleted, it only autocompletes up to derived even though intellisense shows the full name, but I digress)

Another alternative is to NOT use derives and call the derived method by hand:

object Decision {
  given fromBoolean: YesNoEnum[Decision] = YesNoEnum.derived
}

…which is fine enough as well… I guess… But just typing out the whole type ascription is enough to make me not like it as much (since we’re ever so close to perfection!)

The proposal in action

So with my proposal in mind the only thing we’d have to change is adding an as clause to our derives clause, like so:

enum Decision derives YesNoEnum as fromBoolean {
 case Yes, No
}

…and now we can actually call

Decision.fromBoolean(true) // imagine it evaluates to Decision.Yes

without even needing to define a companion object!

POC

I’ve implemented a POC of my proposal here with YesNoEnum actually working the same way I described above.

If there’s actual buy in for something like this I’d like to draft a SIP and spearhead an implementation of it :blush:

3 Likes