有没有人尝试过将 for 推导式与拒绝配置/命令行库一起使用?使用 mapN
及其 Opts
类来生成配置案例类,如果它有很多成员,那么它会变得非常不可读且脆弱。我想使用 for-compression 来代替,如下所示:
val databaseConfig: Opts[DatabaseConfig] = {
for {
username <- Opts.envWithDefault[String]("POSTGRES_USER", "Postgres username", "postgres")
password <- Opts.envWithDefault[String]("POSTGRES_PASSWORD", "Postgres password", "postgres")
hostname <- Opts.envWithDefault[String]("POSTGRES_HOSTNAME", "Postgres hostname", "localhost")
database <- Opts.envWithDefault[String]("POSTGRES_DATABASE", "Postgres database", "thebean")
port <- Opts.envWithDefault[Int]("POSTGRES_PORT", "Postgres port", 5432)
threadPoolSize <- Opts.envWithDefault[Int]("POSTGRES_THREAD_POOL_SIZE", "Postgres thread pool size", 4)
} yield DatabaseConfig(username, password, hostname, database, port, threadPoolSize)
但这似乎是不可能的,因为 Opts
没有定义 flatMap
,而且我没有看到实现它的好方法(这不是说没有)。有什么建议么?我错过了神奇的导入吗?
编辑:
有问题的代码如下所示(真正的问题代码有更多成员,但这给出了想法):
(
Opts.envWithDefault[String]("POSTGRES_USER", "Postgres username", "postgres"),
Opts.envWithDefault[String]("POSTGRES_PASSWORD", "Postgres password", "postgres"),
Opts.envWithDefault[String]("POSTGRES_HOSTNAME", "Postgres hostname", "localhost"),
Opts.envWithDefault[String]("POSTGRES_DATABASE", "Postgres database", "thebean"),
Opts.envWithDefault[Int]("POSTGRES_PORT", "Postgres port", 5432),
Opts.envWithDefault[Int]("POSTGRES_THREAD_POOL_SIZE", "Postgres thread pool size", 4)
).mapN(DatabaseConfig.apply)
如果你想知道使用什么环境变量来设置端口,你必须计数——端口是案例类的第五个成员,所以你必须找到在元组中创建的第五个环境变量。当有很多这样的东西时,这不太好。
评论中建议的以下代码确实有所改进:
val username = Opts.envWithDefault[String]("POSTGRES_USER", "Postgres username", "postgres")
val password = Opts.envWithDefault[String]("POSTGRES_PASSWORD", "Postgres password", "postgres")
val hostname = Opts.envWithDefault[String]("POSTGRES_HOSTNAME", "Postgres hostname", "localhost")
val database = Opts.envWithDefault[String]("POSTGRES_DATABASE", "Postgres database", "thebean")
val port = Opts.envWithDefault[Int]("POSTGRES_PORT", "Postgres port", 5432)
val threadPoolSize = Opts.envWithDefault[Int]("POSTGRES_THREAD_POOL_SIZE", "Postgres thread pool size", 4)
(username, password, hostname, database, port, threadPoolSize).mapN(DatabaseConfig.apply)
但这不正是 for 推导式的目的吗?看起来使用一个会更干净一些,所以我想知道我是否缺少导入或其他东西,或者库是否真的决定让 Opts
上的 flatMap 变得不可能。
最佳答案
So I'm wondering [...] if the library has genuinely decided to make it impossible to
flatMap
over Opts.
是的,他们故意决定避免 flatMap
,因为传递给前面的选项的参数和后面的选项规范之间不应该有因果关系。例如,允许类似
for
username <- Opts.envWithDefault[String]("X", "Postgres username", "W")
password <- Opts.envWithDefault[String]("Y", s"Password of ${username}", "Z")
yield SomeConfig(username, password)
会得出一个荒谬的结论,即需要知道用户名
才能显示密码
的帮助,因为密码
的描述取决于在经过验证的用户名
参数上。这就是 IO
-monad 在交互式对话中的行为方式,但它不适合 Opts
。
它是有意设计成适用性的,而不是单一的。因此,没有 flatMap
,如果他们试图将其强制进入单子(monad)接口(interface),那将是非常奇怪的,这对于这个用例来说是不必要的限制。
所以,而不是
for
x <- m1
y <- m2
z <- m3
yield Foo(x, y, z)
对于单子(monad) m
只需使用
val x = a1
val y = a2
val z = a3
(a1, a2, a3).mapN(Foo.apply)
适用于应用a
s。
关于scala - 拒绝理解,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74449548/