Rewrite implicit Ops class in Scala 3

In Scala 2, I can provide an implicit operation class wrapping the value in performing intended operation.

// scala 2
trait MyTrait[F[_], A] {
  def doSomething[B](f: A => B): F[B]
}

object MyTrait {
  implicit class MyTraitOps[T](value: T) extends MyTrait[Option, T] {
    override def doSomething[B](f: T => B): Option[B] = Option(value).map(f)
  }
}

object App {
  def main(args: Array[String]): Unit = {
    import MyTrait._
    println(3.doSomething(_+1))
  }
}

I tried this in Scala 3 with using and given as well as trait argument, but perhaps I am still not familiar with it. The result I’ve attempted looks more complicated and ugly than I expected. For instance, the given instances can’t be imported with something like MyTrait._ and unlike Scala 2, the function doSomething can not be attached to the instance 3, but the code needs to explicitly supply the value (e.g. using 3). Any suggestions to achieve similar effect - 3.doSomething(_+1) - in Scala 3 with using and given?

// Scala 3
package c
trait MyTrait[F[_], A](using value: A) {
  def doSomething[B](f: A => B): F[B]
}

object MyTrait  {
  def apply[F[_], T](using instance: MyTrait[F, T]): MyTrait[F, T] = instance

  given OptionOps[T](using value: T): MyTrait[Option, T] with
     def doSomething[B](f: T => B): Option[B] = Option(value).map(f)
}

@main
def execute(): Unit = {
  given instance: MyTrait[Option, Int] = c.MyTrait.OptionOps(using 3)
  val f = MyTrait[Option, Int]
  println(f.doSomething(_+1))
}

,
Many thanks.

1 Like

Although I can try with following code, this doesn’t exploit given and using. So I appreciate any commentary to achieve the effect like that in Scala 2 if possible.

object App {
  trait MyTrait[F[_], A] {
    def doSomething[B](f: A => B): F[B]
  }

  case class OptionOps[A](value: A) extends MyTrait[Option, A] {
    def doSomething[B](f: A => B): Option[B] = Option(value).map(f)
  }

  def main(args: Array[String]): Unit = {
    val f = OptionOps(3)
    println(f.doSomething(_+1))
  }
}

Why not just use extension?

1 Like

Wait, what?

My naive translation might look like

// scala 3
trait MyTrait[F[_], A]:
  def doSomething[B](f: A => B): F[B]

object MyTrait:
  extension [A](value: A)
    def doSomething[B](f: A => B): Option[B] = Option(value).map(f)

@main def test() = println {
  import MyTrait.*
  3.doSomething(_+1)
}

but I have not leveraged the trait.

Let me check the docs for writing typeclasses in Scala 3 maybe here

// scala 3
trait MyTypeClass[F[_], A]:
  extension [A](value: A) def doSomething[B](f: A => B): Option[B]

object MyTypeClass:
  given MyTypeClass[Option, Int] with
    extension [A](value: A) def doSomething[B](f: A => B): Option[B] = Option(value).map(f)

@main def test() = println {
  import MyTypeClass.given
  3.doSomething(_+1)
}

Did it work? I guess so, but I had to change import MyTypeClass.* to import the given.

Can I add override because I’m paranoid? OK, yes I can.

extension [A](value: A) override def doSomething 

Can the override go before the extension? No, that is the syntax.

Didn’t I read some discussion about what is the best encoding? I don’t remember if anyone used the old meme where someone is throwing a chair while explaining something. Maybe somebody will point us to the sophisticated talk amongst the smart people.

2 Likes

As @BalmungSan said, you should probably use extension methods here. They cover your use case in a more elegant way than implicit classes and they don’t bring overhead since they desugar into regular methods.

1 Like