在 Scala 中将泛型与 PureConfig 结合使用
Using generic type with PureConfig in Scala
我正在尝试从具有泛型类型的方法调用 PureConfig 的 loadOrThrow:
def load[T: ClassTag](path: String): T = {
import pureconfig.generic.auto._
ConfigSource.file(path).loadOrThrow[T]
}
当我尝试从主 class 调用它时,出现以下错误:
could not find implicit value for parameter reader: pureconfig.ConfigReader[T]
ConfigSource.file(path).loadOrThrow[T]
我可以在 main class 中不使用 import pureconfig.generic.auto._
来解决这个问题吗?
总结评论并解释此编解码器的工作原理。
当你这样做时:
def something[T: ConfigReader] = ...
您正在为
使用语法糖
// Scala 2
def something[T](implicit configReader: ConfigReader[T]) = ...
// Scala 3
def something[T](using configReader: ConfigReader[T]) = ...
在你写的调用站点上:
something[T]
编译器确实如此
something(configReaderForT /* : ConfigReader[T] */)
所以基本上就是编译器支持的基于类型的依赖注入。并且依赖注入必须从某个地方获取要传递的值。
编译器如何获取那个值来传递它?它必须通过范围内的类型找到它。应该有一个明确的最接近此类型的值(或 def
返回此值)标记为 implicit
(Scala 2) 或 given
(Scala 3).
// Scala 2
implicit val fooConfigReader: ConfigReader[Foo] = ...
something[Foo] // will use fooConfigReader
// Scala 3
given fooConfigReader: ConfigReader[Foo] = ...
something[Foo] // will use fooConfigReader
Scala 3 basically made it easier to distinguish which is the definition of value - given
- and which is the place that relies on providing value from somewhere external - using
. Scala 2 has one word for it - implicit
- which was a source of a lot of confusion.
您必须自己定义此 value/method 或导入它 - 在需要它的范围内 - 否则编译器只会尝试查看有助于您的类型的所有类型的伴随对象 T
- 如果 T
是特定的。 (或者如果它在编译器错误消息中的任何地方都找不到它,则会失败)。
// Example of companion object approach
// This is type Foo
case class Foo()
// This is Foo's companion object
object Foo {
// This approach (calling derivation manually) is called semiauto
// and it usually needs a separate import
import pureconfig.generic.semiauto._
implicit val configReader: ConfigReader[Foo] = deriveReader[Foo]
}
// By requiring ConfigReader[Foo] (if it wasn't defined/imported
// into the scope that needs it) compiler would look into:
// * ConfigReader companion object
// * Foo companion object
// ConfigReader doesn't have such instance but Foo does.
如果 T
是通用的,那么您必须将 implicit
/given
作为参数传递 - 但是您只是推迟了必须指定它的时刻,并且让编译器find/generate吧。
// Tells compiler to use value passed as parameter
// as it wouldn't be able to generate it based on generic information
// implicit/using expressed as "type bounds" (valid in Scala 2 and 3)
def something[T: ConfigReader] = ...
// Scala 2
def something[T](implicit configReader: ConfigReader[T]) = ...
// Scala 3
def something[T](using configReader: ConfigReader[T]) = ...
// It works the same for class constructors.
在 PureConfig 的例子中,pureconfig.generic.auto
包含 implicit def
s,它们为指定的 T
生成值。如果你想生成它,你必须将它导入到需要特定实例的地方。您可以在伴随对象中执行此操作,以使其可自动导入到此特定类型需要此 ConfigReader
的任何地方,或将其导入到 main(或任何其他指定 T 的地方)。一种或另一种方式,您将不得不在某个地方派生它,然后将此 [T: ConfigReader]
或 (implicit configReader: ConfigReader[T])
添加到所有不应将 T
硬编码到任何东西的方法的签名中。
总结一下您的选择是:
- 将导入保留在 main 中(如果您硬编码
T
到 main 中的特定类型)
- 派生它并在其他地方定义为
implicit
然后从那里导入它(有些人在 trait
中这样做然后混入它们,但我不喜欢这个方法)
- 派生它并在伴随对象
中定义为implicit
只要你希望你的配置是解析值而不是未类型化的 JSON (HOCON) 而无需自己编写这些编解码器,你就必须在某处执行自动(或半自动)推导。
我正在尝试从具有泛型类型的方法调用 PureConfig 的 loadOrThrow:
def load[T: ClassTag](path: String): T = {
import pureconfig.generic.auto._
ConfigSource.file(path).loadOrThrow[T]
}
当我尝试从主 class 调用它时,出现以下错误:
could not find implicit value for parameter reader: pureconfig.ConfigReader[T]
ConfigSource.file(path).loadOrThrow[T]
我可以在 main class 中不使用 import pureconfig.generic.auto._
来解决这个问题吗?
总结评论并解释此编解码器的工作原理。
当你这样做时:
def something[T: ConfigReader] = ...
您正在为
使用语法糖// Scala 2
def something[T](implicit configReader: ConfigReader[T]) = ...
// Scala 3
def something[T](using configReader: ConfigReader[T]) = ...
在你写的调用站点上:
something[T]
编译器确实如此
something(configReaderForT /* : ConfigReader[T] */)
所以基本上就是编译器支持的基于类型的依赖注入。并且依赖注入必须从某个地方获取要传递的值。
编译器如何获取那个值来传递它?它必须通过范围内的类型找到它。应该有一个明确的最接近此类型的值(或 def
返回此值)标记为 implicit
(Scala 2) 或 given
(Scala 3).
// Scala 2
implicit val fooConfigReader: ConfigReader[Foo] = ...
something[Foo] // will use fooConfigReader
// Scala 3
given fooConfigReader: ConfigReader[Foo] = ...
something[Foo] // will use fooConfigReader
Scala 3 basically made it easier to distinguish which is the definition of value -
given
- and which is the place that relies on providing value from somewhere external -using
. Scala 2 has one word for it -implicit
- which was a source of a lot of confusion.
您必须自己定义此 value/method 或导入它 - 在需要它的范围内 - 否则编译器只会尝试查看有助于您的类型的所有类型的伴随对象 T
- 如果 T
是特定的。 (或者如果它在编译器错误消息中的任何地方都找不到它,则会失败)。
// Example of companion object approach
// This is type Foo
case class Foo()
// This is Foo's companion object
object Foo {
// This approach (calling derivation manually) is called semiauto
// and it usually needs a separate import
import pureconfig.generic.semiauto._
implicit val configReader: ConfigReader[Foo] = deriveReader[Foo]
}
// By requiring ConfigReader[Foo] (if it wasn't defined/imported
// into the scope that needs it) compiler would look into:
// * ConfigReader companion object
// * Foo companion object
// ConfigReader doesn't have such instance but Foo does.
如果 T
是通用的,那么您必须将 implicit
/given
作为参数传递 - 但是您只是推迟了必须指定它的时刻,并且让编译器find/generate吧。
// Tells compiler to use value passed as parameter
// as it wouldn't be able to generate it based on generic information
// implicit/using expressed as "type bounds" (valid in Scala 2 and 3)
def something[T: ConfigReader] = ...
// Scala 2
def something[T](implicit configReader: ConfigReader[T]) = ...
// Scala 3
def something[T](using configReader: ConfigReader[T]) = ...
// It works the same for class constructors.
在 PureConfig 的例子中,pureconfig.generic.auto
包含 implicit def
s,它们为指定的 T
生成值。如果你想生成它,你必须将它导入到需要特定实例的地方。您可以在伴随对象中执行此操作,以使其可自动导入到此特定类型需要此 ConfigReader
的任何地方,或将其导入到 main(或任何其他指定 T 的地方)。一种或另一种方式,您将不得不在某个地方派生它,然后将此 [T: ConfigReader]
或 (implicit configReader: ConfigReader[T])
添加到所有不应将 T
硬编码到任何东西的方法的签名中。
总结一下您的选择是:
- 将导入保留在 main 中(如果您硬编码
T
到 main 中的特定类型) - 派生它并在其他地方定义为
implicit
然后从那里导入它(有些人在trait
中这样做然后混入它们,但我不喜欢这个方法) - 派生它并在伴随对象 中定义为
implicit
只要你希望你的配置是解析值而不是未类型化的 JSON (HOCON) 而无需自己编写这些编解码器,你就必须在某处执行自动(或半自动)推导。