Scala – Make implicit value classes available in another scope
Asked Answered
B

1

7

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
Baerman answered 9/2, 2017 at 4:5 Comment(0)
P
7

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
  }
}
Playgoer answered 9/2, 2017 at 4:38 Comment(5)
This is the correct response, but I would disagree with the advice about not using implicits. Implicits are an incredibly powerful feature in scala which enable "zero cost" (not quite) syntax enrichment, as well as the Typeclass pattern.Greco
I don't know about the correct response, but Herrington Darkholme is definitely the right name. I'd also go with Hugo Firth. I'm conflicted.Mercaptopurine
Fairly, it's not my suggestion but scala compiler's, if you add -feature argument when compiling, compiler will warn you about implicit usage. Resolving implicits is quite involved, see the tutorial I linked above.Playgoer
Edited answer and remove comments on implicit usage. OP wants an answer, not a sermon.Playgoer
Amazing, thank you very much! I haven't seen that article before, reading now. Nice technique making value classes non-implicit. Even though we explicitly call new SuperFoo, it does not generate runtime allocation (I checked with javap). I mean, it's kinda obvious in retrospect, value classes don't need to be implicit to avoid runtime allocation.Baerman

© 2022 - 2024 — McMap. All rights reserved.