SYB `cast` function in Scala
Asked Answered
U

1

0

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?

Unnecessary answered 21/10, 2022 at 4:6 Comment(0)
C
3

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)

Copperas answered 21/10, 2022 at 4:16 Comment(3)
Yep, that was it, thank you! I'm curious now, what was happening at compile time without that? Why was it that cNone was the instance being summoned?Unnecessary
@Unnecessary Please see the link about the difference implicitly[X] vs. (implicit x: X) in Scala 2 (or summon[X] vs. (using X) in Scala 3).Copperas
Thanks for the link, that makes sense, the difference between declaration vs use-site implicit resolution. At the declaration, there's nothing saying A =:= BUnnecessary

© 2022 - 2024 — McMap. All rights reserved.