Какая функциональная техника позволяет не передавать конфигурацию через функции - PullRequest
2 голосов
/ 24 апреля 2019

Поскольку я углубляюсь в FP, меня интересует «лучший» способ хранения настроек, которые загружаются из конфигурационных файлов.Я только что создал класс case со всеми необходимыми переменными конфигурации и установил его при запуске приложения.Затем я передаю этот класс case в любую функцию, для которой требуется информация.

Тем не менее, это выглядит довольно раздражающим, особенно когда этот класс настроек должен распространяться во многих функциях.Есть ли лучший способ сделать это?

1 Ответ

2 голосов
/ 25 апреля 2019

Reader монада обеспечивает способ распространения конфигурации без передачи ее в качестве параметра во все функции, которые в ней нуждаются.Сравните следующие две реализации:

Конфигурация доступна из контекста через 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 результат."

«Контекст» здесь представлен Reader монадой.

Кроме того, обратите внимание, что 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...