Allowing custom singleton types (ValueOf[T])

In its current state, Scala provides singleton types for literal and “primitives” types. However, some libs like Refined, Iron etc… will profit from more complex singleton types like case class-based ones.

I am wondering if it is possible to achieve this by transforming ValueOf into an inline typeclass looking like this:

trait ValueOf[T] {

  inline def value: T

Here is an implementation example:

case class Complex[R <: Double, I <: Double](re: R, im: I)

inline given [R <: Double : ValueOf, I <: Double : ValueOf]: ValueOf[Complex[R, I]] {

  override inline def value: Complex[R, I] = Complex(valueOf[R], valueOf[I])

valueOf[Complex[1d, 2d]] //Complex(1d, 2d)

This method is probably not the most “powerful” but I feel it is the most “vanilla”.
This is probably combinable with case class derivation.

Note this idea probably has several caveats which are not covered in this post. Feel free to point them out since it is a draft far from being finished.

1 Like

I don’t understand the benefit of this. In Scala 3 we have scala.compiletime.ops.

1 Like

scala.compiletimes.ops was taken as an example to show how we could create custom type ops using this method but the main motivation of this is to use more complex singleton types than literals (see the example with Complex). The less “magic” way I see at the moment is to use ValueOf.

As particular use-case would be refined types (at compile-time).

But as I said, this draft is far from being perfect and there’s possibly a better way to implement case class-based singleton types. I’ve seen some people mentionning this (case class-based singletons, not this specific impl) in dotty’s issues and I share this need.

Edit: I edited the first post to be clearer. The operator example is not that important or relevant here.

OK, so checkout this proposal if it fits you:

1 Like

The solution with a dependent type instead of an inline def for custom operators looks very elegant. This also seems to allow richer types (like case class-based) since you can use a parameter-less operator.