什么功能技术使不必通过函数传递配置



当我深入研究FP时,我很好奇存储从配置文件加载的设置的"最佳"方法。我刚刚创建了一个包含所有必要配置变量的案例类,并在应用程序启动时进行了设置。然后,我将该 case 类传递给需要它提供信息的任何函数。

但是,这似乎很烦人,尤其是当该设置案例类必须通过许多函数传播时。有没有更好的方法可以做到这一点?

Readermonad提供了一种传播配置的方法,而不必将其作为参数传递给所有需要它的函数。对比以下两种实现:

可通过Reader[Config, String]从上下文中获得配置

object ConfigFunctional extends App {
case class Config(username: String, password: String, host: String)
def encodeCredentials: Reader[Config, String] = Reader { config =>
Base64.getEncoder.encodeToString(s"${config.username}:${config.password}".getBytes())
}
def basicAuth(credentials: String): Reader[Config, String] = Reader { config =>
Http(s"${config.host}/HTTP/Basic/")
.header("Authorization", s"Basic $credentials")
.asString
.body
}
def validateResponse(body: String): Reader[Config, Either[String, String]] = Reader { _ =>
if (body.contains("Your browser made it"))
Right("Credentials are valid!")
else
Left("Wrong credentials")
}
def program: Reader[Config, Either[String, String]] = for {
credentials       <- encodeCredentials
response          <- basicAuth(credentials)
validation        <- validateResponse(response)
} yield validation

val config = Config("guest", "guest", "https://jigsaw.w3.org")
println(program.run(config))
}

作为参数传入的配置

object ConfigImperative extends App {
case class Config(username: String, password: String, host: String)
def encodeCredentials(config: Config): String = {
Base64.getEncoder.encodeToString(s"${config.username}:${config.password}".getBytes())
}
def basicAuth(credentials: String, config: Config): String = {
Http(s"${config.host}/HTTP/Basic/")
.header("Authorization", s"Basic $credentials")
.asString
.body
}
def validateResponse(body: String): Either[String, String] = {
if (body.contains("Your browser made it"))
Right("Credentials are valid!")
else
Left("Wrong credentials")
}
def program(config: Config): Either[String, String] = {
val credentials = encodeCredentials(config)
val response    = basicAuth(credentials, config)
val validation  = validateResponse(response)
validation
}
val config = Config("guest", "guest", "https://jigsaw.w3.org")
println(program(config))
}

两个实现都应该输出Right(Credentials are valid!),但请注意,在第一个实现中config: Config不是方法参数,例如,对比度encodeCredentials

def encodeCredentials: Reader[Config, String]
def encodeCredentials(config: Config): String

Config显示在返回类型中,而不是参数。我们可以将其解释为意义

"当encodeCredentials在提供Config,那么它会产生String的结果。

这里的"上下文"由Readermonad 表示。

此外,请注意,即使在主业务逻辑中,Config也不是参数

def program: Reader[Config, Either[String, String]] = for {
credentials       <- encodeCredentials
response          <- basicAuth(credentials)
validation        <- validateResponse(response)
} yield validation

我们让方法在包含Config的上下文中通过run函数进行计算:

program.run(config)

要运行上述示例,我们需要以下依赖项

scalacOptions += "-Ypartial-unification",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "1.6.0", 
"org.scalaj" %% "scalaj-http" % "2.4.1"
)

和进口

import cats.data.Reader
import java.util.Base64
import scalaj.http.Http

最新更新