Loading a ficus configuration like
loadConfiguration[T <: Product](): T = {
import net.ceedubs.ficus.readers.ArbitraryTypeReader._
import net.ceedubs.ficus.Ficus._
val config: Config = ConfigFactory.load()
config.as[T]
fails with:
Cannot generate a config value reader for type T, because it has no apply method in a companion object that returns type T, and it doesn't have a primary constructor
when instead directly specifying a case class instead of T
i.e. SomeClass
it works just fine. What am I missing here?
Ficus uses the type class pattern, which allows you to constrain generic types by specifying operations that must be available for them. Ficus also provides type class instance "derivation", which in this case is powered by a macro that can inspect the structure of a specific case class-like type and automatically create a type class instance.
The problem in this case is that
T
isn't a specific case class-like type—it's any old type that extendsProduct
, which could be something nice like this:But it could also be:
The macro you've imported from
ArbitraryTypeReader
has no idea at this point, sinceT
is generic here. So you'll need a different approach.The relevant type class here is
ValueReader
, and you could minimally change your code to something like the following to make sureT
has aValueReader
instance (note that theT: ValueReader
syntax here is what's called a "context bound"):This specifies that
T
must have aValueReader
instance (which allows us to use.as[T]
) but says nothing else aboutT
, or about where itsValueReader
instance needs to come from.The person calling this method with a concrete type
MyType
then has several options. Ficus provides instances that are automatically available everywhere for many standard library types, so ifMyType
is e.g.Int
, they're all set:If
MyType
is a custom type, then either they can manually define their ownValueReader[MyType]
instance, or they can import one that someone else has defined, or they can use generic derivation (which is whatArbitraryTypeReader
does).The key point here is that the type class pattern allows you as the author of a generic method to specify the operations you need, without saying anything about how those operations will be defined for a concrete type. You just write
T: ValueReader
, and your caller importsArbitraryTypeReader
as needed.