Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support @newsubtype for value classes #73

Open
steinybot opened this issue Jan 5, 2023 · 0 comments
Open

Support @newsubtype for value classes #73

steinybot opened this issue Jan 5, 2023 · 0 comments

Comments

@steinybot
Copy link

I needed a new sub-type / tagged type for a value class. It had to be a sub-type because I needed to use it in all the places where the base type could be used but perhaps more importantly I needed all the implicit conversions to be found from its companion object.

The problem with @newsubtype is that it doesn't support value classes. One reason is because the Ops$newtype is a value class which cannot wrap another user-defined value class.

I tried doing something more like what Shapeless does but then I run into a bunch of ClassCastExceptions. I initially reported that in scala-js/scala-js#4778 as I thought it was a Scala.js specific issue but that turns out to be untrue. I found a bunch of related Shapeless issues (see that issue for links).

I started wondering how it was that @newtype avoided these ClassCastExceptions (and as hinted by Miles in some of his comments that Shapeless 3 may change Tagged back to how it was). It seems that the structural type is the key here.

We of course need the type we are extending somewhere in Type for it to be a subtype but it can't be in Base.

This encoding seems to do the trick:

import scala.language.implicitConversions

object Main {

  final class KeyAddingStage(private val args: Array[Any]) extends AnyVal

  object KeyAddingStage {
    implicit def build(stage: KeyAddingStage): String = stage.toString
  }

  type RenderedProps[Props] = RenderedProps.Type[Props]

  object RenderedProps {
    type Base = {
      type __RenderedProps__newtype
    }

    trait Tag[Props] extends Any

    type Type[Props] <: Base with KeyAddingStage with Tag[Props]

    def apply[Props](stage: KeyAddingStage): RenderedProps[Props] = stage.asInstanceOf[RenderedProps[Props]]
  }

  object Name {
    case class Props(name: String)

    def component(props: Props): KeyAddingStage = new KeyAddingStage(
      Array(
        props.asInstanceOf[Any]
      )
    )

    def apply(name: String): RenderedProps[Props] = RenderedProps(component(Props(name)))
  }

  def main(args: Array[String]): Unit = {
    val first: RenderedProps[Name.Props] = Name("Jason")
    val firstStage: KeyAddingStage = first
    val str: String = first
    println(first == firstStage)
    val props: Seq[RenderedProps[Name.Props]] = Seq(first)
    val propsHead: RenderedProps[Name.Props] = props.head
    println(firstStage == propsHead)
  }
}

Any thoughts on adding something like this so that we can have @newsubtype support for value classes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant