Search code examples
scalaimplicitshapelessscrap-your-boilerplatesyb

SYB `cast` function in Scala


I am reading the Scrap Your Boilerplate paper and trying to follow along by implementing the ideas in scala as best I can. However, I'm stuck on the very first function, the cast, which is used to take a value and attempt to cast it to another type in an Option, obviously a Some if the cast is successful, and None otherwise. I have a version of it working with the following code:

  trait Cast[A, B]:
    def apply(a: A): Option[B]

  object Cast:
    given cSome[A, B](using t: A =:= B): Cast[A, B] with
      def apply(a: A) = Some(t(a))

    given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] with
      def apply(a: A) = None

This works when types are statically known. Trying the examples from the paper:

  val maybeChar = summon[Cast[Char, Char]]('a')  // Some(a)
  val maybeBool = summon[Cast[Char, Boolean]]('a')  // None
  val maybeBool2 = summon[Cast[Boolean, Boolean]](true) // Some(true)

However, when I try to clean up the ergonomics a bit so that we can rely on type inference as in the examples in the paper with a generic helper defined as such:

  def cast[A, B](a: A): Option[B] = summon[Cast[A, B]](a)

I'm getting only Nones, meaning the types are never being seen as the same:

  val mc: Option[Char] = cast('a')  // None
  val mb: Option[Boolean] = cast('a')  // None 
  val mb2: Option[Boolean] = cast(true)  // None

The same happens even when I'm being explicit with the types:

  val mc: Option[Char] = cast[Char, Char]('a')  // None
  val mb: Option[Boolean] = cast[Char, Boolean]('a')  // None
  val mb2: Option[Boolean] = cast[Boolean, Boolean](true)  // None

I'm using scala 3.2. Is there any way to achieve this cast function with the less verbose ergonomics? I'm even more curious why what I have isn't working, especially with the explicit casting? I'm pretty sure shapeless is able to provide an SYB implementation in scala 2, albeit probably relying on macros. Can we do this in scala 3 without macros?


Solution

  • You missed an implicit parameter

    def cast[A, B](a: A)(using Cast[A, B]): Option[B] = summon[Cast[A, B]](a)
    //                   ^^^^^^^^^^^^^^^^   
    

    When doing implicit resolution with type parameters, why does val placement matter? (implicitly[X] vs. (implicit x: X) in Scala 2, summon[X] vs. (using X) in Scala 3)