scala 隐式pureconfig ConfigReaders的显式类型

7lrncoxx  于 2023-08-05  发布在  Scala
关注(0)|答案(2)|浏览(113)

Scala v2.13.11对pureconfig orElse documentation(页面底部)中的以下代码发出警告:

val csvIntListReader = ConfigReader[String].map(_.split(",").map(_.toInt).toList)
implicit val intListReader = ConfigReader[List[Int]].orElse(csvIntListReader)

case class IntListConf(list: List[Int])

字符串
警告是:

[warn] Implicit definition should have explicit type (inferred pureconfig.ConfigReader[List[Int]])
[warn]   implicit val intListReader = ConfigReader[List[Int]].orElse(csvIntListReader)
[warn]                ^


当我添加推断类型ConfigReader[List[Int]]时,它编译时没有警告,但我在运行时得到一个NullPointerException
这给我提出了以下问题:
1.为什么当我们让编译器推断类型时,这是可行的,但当我们显式提供编译器说它推断的类型时,它却不起作用?
1.有没有一种类型可以显式地赋予intListReader,使其编译时没有警告,运行时没有错误?
1.如果3是不可能的,那么添加@nowarn是否“安全”(例如仍然可以使用Scala 3)?
谢谢你的见解。
PS:我的运行时测试也来自文档:

ConfigSource.string("""{ list = [1,2,3] }""").load[IntListConf] ==> Right(IntListConf(List(1, 2, 3)))


和/或

ConfigSource.string("""{ list = "4,5,6" }""").load[IntListConf] ==> Right(IntListConf(List(4, 5, 6)))

ogq8wdun

ogq8wdun1#

这是一个未记录的未定义行为。
当您这样做时:

implicit val x: X = implicitly[X]

字符串
编译器将生成

implicit val x: X = x


这取决于上下文(在你有它的地方,它是vallazy val还是def)将结束:

  • 编译错误(如您所见)
  • NullPointerException或InitializationError
  • StackOverflowException

通常,您希望将其解析为:

// someCodeGenerator shouldn't use x
implicit val x: X = implicitly[X](someCodeGenerator)


这将使用一些机制来避免在计算隐式的过程中使用x。例如,通过使用一些 Package 器或子类型来解包/上转换(例如,在Circe中,您要求使用semiauto导出的Encoder/Decoder,并且通过隐式获得的是一些DerivedEncoder/DerivedDecoder来解包/上投)。
这是定义的行为,当你的作用域中的所有隐式定义都被注解时,隐式是如何被解析的。
如果有些人不像这个人会怎么样?

  • 在计算intListReader时,编译器被询问有关隐式ConfigReader[List[Int]]的信息
  • 在隐式作用域中,没有像这样显式注解的值
  • 有一个类型必须被推断(intListReader)
  • 这里的行为是未定义和未记录的,但编译器作者决定跳过此隐式并继续
  • 计算值时不使用此隐式
  • 值(intListReader)的类型计算为ConfigReader[List[Int]]
  • 该定义下面的代码可以看到该类型的隐式

一般来说,库不应该依赖于这种行为,应该有一些半自动派生为您提供,在Scala(3.x)的未来版本中,这段代码是非法的,所以我建议将其重写为semiauto。
在您的特定情况下,您还可以使用一个技巧,调用与返回的不同的隐式:

implicit val intListReader: ConfigReader[List[Int]] =
  ConfigReader[Vector[Int]].map(_.toList) // avoids self-summoning
    .orElse(
      ConfigReader[String].map(_.split(",").map(_.toInt).toList)
    )


但是如果它不是一个内置支持的类型(它们不适用于deriveReader,因为它只为sealedcase class定义),你可以用途:

import pureconfig.generic.semiauto._

implicit val intListConf: ConfigReader[IntListConf] =
  deriveReader[IntListConf] // NOT refering intListConf

3phpmpom

3phpmpom2#

当您提供显式类型时,所有内容都以显式方式工作。
这里的NullPointerException是由运行时提供的值引起的运行时异常。这意味着配置中提供的值是罪魁祸首。
当配置包含空字符串时,您的ConfigReader_.split(",")上失败。
只需在代码中处理null的可能性。

val csvIntListReader = 
  ConfigReader[String].map { s =>
    Option(s) match {
      case None => List.empty[Int]
      case Some(ss) => ss.split(",").map(_.toInt).toList
    }
  }

字符串
这将允许从空字符串到空列表的转换。但当提供的列表包含非整数值时,这仍然会失败。
另外,尽量总是使用显式类型和隐式值,否则你最终会得到一个需要跟踪的推断隐式迷宫。
Scala类型推断的工作原理是,通过返回使用信息,在允许的可能类型中为所提供的值选择类型。
implicit的情况下,Scala编译器希望从提供的类型的可用implicit值中选择最佳值。
你现在应该能看到问题所在了。。当你让编译器决定提供值的类型和补充类型的值时...编译器对此不会很高兴。

相关问题