Last active
January 7, 2019 14:32
-
-
Save eneveu/32c410382addc8c22331c3064cb06d71 to your computer and use it in GitHub Desktop.
Should I use "implicit def" or "implicit class" for an API ?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package implicitclass | |
import scala.collection.JavaConverters._ | |
import scala.collection.immutable._ | |
import com.typesafe.config.{ Config, ConfigFactory } | |
trait ConfigSupport { | |
def customLoadConfig(): Config = ConfigFactory.load() | |
} | |
object ConfigSupport extends ConfigSupport | |
object ConfigImplicits { | |
implicit class RichConfig(val config: Config) extends AnyVal { | |
def toMap: Map[String, String] = { | |
config.entrySet().asScala.map(entry ⇒ (entry.getKey, entry.getValue.unwrapped().toString)).toMap | |
} | |
} | |
} | |
object UsageWithTrait extends ConfigSupport { | |
def test(): Unit = { | |
import ConfigImplicits._ | |
val config = customLoadConfig() | |
val map = config.toMap | |
} | |
} | |
object UsageWithObject { | |
def test(): Unit = { | |
import ConfigImplicits._ | |
import ConfigSupport._ | |
val config = customLoadConfig() | |
val map = config.toMap | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package implicitdef | |
import scala.collection.JavaConverters._ | |
import scala.collection.immutable._ | |
import com.typesafe.config.{ Config, ConfigFactory } | |
trait ConfigSupport { | |
def customLoadConfig(): Config = ConfigFactory.load() | |
implicit def enrichConfig(config: Config): RichConfig = new RichConfig(config) | |
} | |
object ConfigSupport extends ConfigSupport | |
class RichConfig(val config: Config) extends AnyVal { | |
def toMap: Map[String, String] = { | |
config.entrySet().asScala.map(entry ⇒ (entry.getKey, entry.getValue.unwrapped().toString)).toMap | |
} | |
} | |
object UsageWithTrait extends ConfigSupport { | |
def test(): Unit = { | |
val config = customLoadConfig() | |
val map = config.toMap | |
} | |
} | |
object UsageWithObject { | |
def test(): Unit = { | |
import ConfigSupport._ | |
val config = customLoadConfig() | |
val map = config.toMap | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package notraitimplicitclass | |
import scala.collection.JavaConverters._ | |
import scala.collection.immutable._ | |
import com.typesafe.config.{ Config, ConfigFactory } | |
object ConfigSupport { | |
def customLoadConfig(): Config = ConfigFactory.load() | |
implicit class RichConfig(val config: Config) extends AnyVal { | |
def toMap: Map[String, String] = { | |
config.entrySet().asScala.map(entry ⇒ (entry.getKey, entry.getValue.unwrapped().toString)).toMap | |
} | |
} | |
} | |
object Usage { | |
def test(): Unit = { | |
import ConfigSupport._ | |
val config = customLoadConfig() | |
val map = config.toMap | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I'm designing an internal library to simplify config code in our projects. This library will have utility methods (e.g.
def customLoadConfig()
) and implicit extension methods (e.g.RichConfig#toMap()
).I often see a pattern of providing a trait
Foo
and a companion object extending that trait, to let users choose betweenextends Foo
orimport Foo._
(useful in the Scala REPL). There is an example of this in Scalatra. But I can't put animplicit class Xxx extends AnyVal
inside a trait, because value classes "must be a top-level class or a member of a statically accessible object".I have multiple choices :
ImplicitClass.scala
)implicit def
instead, and move theRichConfig
outside the trait (seeImplicitDef.scala
)ConfigSupport
object that users will import (seeNoTraitImplicitClass.scala
)AnyVal
and take the negligibel performance hit (in this case, it wouldn't be a problem, since configuration code is usually called only once at startup, but it could be more problematic in performance-critical code that is called in a loop)