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
Tisn'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
ArbitraryTypeReaderhas no idea at this point, sinceTis 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 sureThas aValueReaderinstance (note that theT: ValueReadersyntax here is what's called a "context bound"):This specifies that
Tmust have aValueReaderinstance (which allows us to use.as[T]) but says nothing else aboutT, or about where itsValueReaderinstance needs to come from.The person calling this method with a concrete type
MyTypethen has several options. Ficus provides instances that are automatically available everywhere for many standard library types, so ifMyTypeis e.g.Int, they're all set:If
MyTypeis 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 whatArbitraryTypeReaderdoes).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 importsArbitraryTypeReaderas needed.