scala - 什么功能技术使得不必通过功能传递配置

标签 scala playframework functional-programming

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

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

最佳答案

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

 通过 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 出现在返回类型中,而不是作为参数。我们可以将其解释为含义

"When encodeCredentials runs in the context that provides a Config, then it will produce a String result."

这里的“上下文”由Reader monad 表示。

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

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

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

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

关于scala - 什么功能技术使得不必通过功能传递配置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55833420/

相关文章:

postgresql - Ebean 中的 Heroku Postgres bytea

java - AJAX POST Play Framework 中复杂元素的绑定(bind)列表

scala - 具有 Circe 实现的通用 json 解码器特征

javascript - 为什么在支持 UnderscoreJS 的重命名函数中使用 apply ?

.net - 您将 f# 用于哪些代码领域?

f# - 如何保证F#应用程序中的参照透明性?

scala - 在 scala 测试模块中使用 gradle 运行特定测试

scala - 在Scala或Java中,如何在控制台打印一行以替换其先前内容而不是附加内容?

Scala List.filter 有两个条件,只应用一次