Search code examples
scalaimplicit-conversionscoping

Scala – Make implicit value classes available in another scope


I have a package foo which contains class FStream. The package object of foo defines a few implicit value classes that provide extender methods for FStream. I would like to move these value classes out of the package object and into their own individual files, but I also want them to always be available when I use FStream (or preferably, when I use anything from foo package. Is it possible to accomplish this? I tried putting implicit value classes into other objects, but I can't extend from objects. Tried putting them in classes or traits, but implicit value classes can only be defined in other objects.

foo/FStream.scala

package foo

class FStream {
  def makeFoo(): Unit = ???
}

foo/package.scala

package foo

package object foo {

  // I want to move these definitions into separate files:

  implicit class SuperFoo(val stream: FStream) extends AnyVal {
    def makeSuperFoo(): Unit = ???
  }

  implicit class HyperFoo(val stream: FStream) extends AnyVal {
    def makeHyperFoo(): Unit = ???
  }
} 

bar/usage.scala

package bar

import foo._ // something nice and short that doesn't reference individual value classes

val x: FStream = ???
x.makeSuperFoo() // should work
x.makeHyperFoo() // should work

Solution

  • I recommend you to read the mandatory tutorial first.

    My solution is to use FStream's companion object. So you can just import FStream and get all the functionality. This also uses trait to separate files.

    foo/FStream.scala

    package foo
    
    class FStream {
      def makeFoo(): Unit = ???
    }
    
    // companion provides implicit
    object FStream extends FStreamOp
    

    foo/FStreamOp.scala

    package foo
    
    // value class may not be a member of another class
    class SuperFoo(val stream: FStream) extends AnyVal {
      def makeSuperFoo(): Unit = ???
    }
    
    class HyperFoo(val stream: FStream) extends AnyVal {
      def makeHyperFoo(): Unit = ???
    }
    trait FStreamOp {
      // you need to provide separate implicit conversion
      implicit def makeSuper(stream: FStream) = new SuperFoo(stream)
      implicit def makeHyper(stream: FStream) = new HyperFoo(stream)
    }
    

    usage.scala

    import foo.FStream
    
    object Main {
      def main(args: Array[String]): Unit = {
        val x: FStream = ???
        x.makeSuperFoo() // should work
        x.makeHyperFoo() // should work
      }
    }