我有一些枚举实现为密封特征和案例对象。我之所以喜欢使用ADT方法,是因为它没有详尽的警告,并且主要是因为我们想避免类型擦除。像这样
sealed abstract class Maker(val value: String) extends Product with Serializable {
override def toString = value
}
object Maker {
case object ChryslerMaker extends Vendor("Chrysler")
case object ToyotaMaker extends Vendor("Toyota")
case object NissanMaker extends Vendor("Nissan")
case object GMMaker extends Vendor("General Motors")
case object UnknownMaker extends Vendor("")
val tipos = List(ChryslerMaker, ToyotaMaker, NissanMaker,GMMaker, UnknownMaker)
private val fromStringMap: Map[String, Maker] = tipos.map(s => s.toString -> s).toMap
def apply(key: String): Option[Maker] = fromStringMap.get(key)
}
到目前为止,它运行良好,现在我们正在考虑向其他程序员提供对我们代码的访问权限,以允许他们在现场进行配置。我看到两个潜在的问题: 1)人们搞砸了,写着类似的东西:
case object ChryslerMaker extends Vendor("Nissan")
人们忘记更新Tipos
我一直在研究使用配置文件(JSON或csv)来提供这些值并像处理其他许多元素一样读取它们,但是我发现的所有答案都依赖于宏,并且似乎极其依赖使用的scala版本(对我们来说是2.12)。
我想找到的是: 1a)(首选)一种从字符串列表中动态创建案例对象的方法,以确保对象名称与其持有的值一致 1b)(可接受),如果这证明在测试阶段很难获得对象和值的方法 2)检查列表中元素的数量是否与创建的案例对象的数量相匹配。
我忘了提一下,我只是简要地列举了枚举,但我不希望包括其他库,除非我真正了解其优缺点(如果您认为,现在我不确定与ADT方法相比枚举有多大这是最好的方法,可以将我引向进行有效的讨论)
谢谢!
答案 0 :(得分:2)
我想到的一个想法是创建一个SBT SourceGenerator
task。
这将读取输入的JSON,CSV,XML或任何文件,这是您的项目的一部分,并将创建一个scala文件。
// ----- File: project/VendorsGenerator.scala -----
import sbt.Keys._
import sbt._
/**
* An SBT task that generates a managed source file with all Scalastyle inspections.
*/
object VendorsGenerator {
// For demonstration, I will use this plain List[String] to generate the code,
// you may change the code to read a file instead.
// Or maybe this will be good enough.
final val vendors: List[String] =
List(
"Chrysler",
"Toyota",
...
"Unknow"
)
val generatorTask = Def.task {
// Make the 'tipos' List, which contains all vendors.
val tipos =
vendors
.map(vendorName => s"${vendorName}Vendor")
.mkString("val tipos: List[Vendor] = List(", ",", ")")
// Make a case object for each vendor.
val vendorObjects = vendors.map { vendorName =>
s"""case object ${vendorName}Vendor extends Vendor { override final val value: String = "${vendorName}" }"""
}
// Fill the code template.
val code =
List(
List(
"package vendors",
"sealed trait Vendor extends Product with Serializable {",
"def value: String",
"override final def toString: String = value",
"}",
"object Vendors extends (String => Option[Vendor]) {"
),
vendorObjects,
List(
tipos,
"private final val fromStringMap: Map[String, Vendor] = tipos.map(v => v.toString -> v).toMap",
"override def apply(key: String): Option[Vendor] = fromStringMap.get(key.toLowerCase)",
"}"
)
).flatten
// Save the new file to the managed sources dir.
val vendorsFile = (sourceManaged in Compile).value / "vendors.scala"
IO.writeLines(vendorsFile, code)
Seq(vendorsFile)
}
}
现在,您可以激活源生成器了。
每次在编译步骤之前运行此任务。
// ----- File: build.sbt -----
sourceGenerators in Compile += VendorsGenerator.generatorTask.taskValue
请注意,我建议您这样做,因为我之前做过,而且没有任何宏或元编程经验。
此外,请注意,此示例在Strings中传递了很多内容,这使得代码有点难以理解和维护。
顺便说一句,我没有使用enumeratum
,但快速浏览一下似乎是解决此问题的最佳方法
我准备好要读取HOCON文件并生成匹配代码的代码。现在我的问题是将scala文件放在项目目录中的位置,以及在哪里生成文件。我有点困惑,因为似乎有多个步骤:1)编译我的scala生成器,2)运行生成器,以及3)编译并生成项目。是这样吗?
您的生成器不是您的项目代码的一部分,而是您的元项目(我知道这听起来很令人困惑,您可以阅读this来理解它)-这样,您将生成器放在根级别(位于指定{strong> sbt版本 的project
文件的文件夹中)的build.properties
文件夹内。
如果您的生成器需要某些依赖项(我确定它确实可以读取 HOCON ),则可以将其放在build.sbt
内的project
文件中文件夹。
如果您打算将单元测试添加到生成器,则可以在元项目中创建一个完整的scala项目(您可以看一下开源项目的project
folder(是的,我知道,再次使我感到困惑)(我在此工作以作参考)-我的个人建议是,除了测试生成器本身之外,还应该测试生成的文件,或者两者都更好。
生成的文件将自动放置在src_managed
文件夹(位于target
内)中,因此将从源代码版本控制中将其忽略)。
里面的路径只是 order ,因为src_managed
文件夹中的所有内容在编译时默认都包括在内。
val vendorsFile = (sourceManaged in Compile).value / "vendors.scala" // Path to the file to write.`
为了访问源代码上生成的文件中定义的值,您只需要将包添加到生成的文件中,然后从您的代码中将该包中的值导入(与任何普通文件一样) 。
您无需担心与编译顺序有关的任何事情,如果您将源代码生成器包含在build.sbt
文件中, SBT 将自动处理所有事情。
sourceGenerators in Compile += VendorsGenerator.generatorTask.taskValue // Activate the source generator.
SBT 将在每次需要编译时重新运行您的生成器。
“顺便说一句,我在导入中得到“未找到:对象sbt”。
如果该项目位于元项目空间内,则默认情况下它将找到sbt包,不用担心。