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 hidden or 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 hidden or 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 hidden or 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 | |
| } | |
| } |
Author
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
Fooand a companion object extending that trait, to let users choose betweenextends Fooorimport Foo._(useful in the Scala REPL). There is an example of this in Scalatra. But I can't put animplicit class Xxx extends AnyValinside 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 definstead, and move theRichConfigoutside the trait (seeImplicitDef.scala)ConfigSupportobject that users will import (seeNoTraitImplicitClass.scala)AnyValand 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)