#scala #playframework #functional-programming
#scala #playframework #функциональное программирование
Вопрос:
Поскольку я все больше углубляюсь в FP, мне любопытно узнать о «лучшем» способе сохранения настроек, загруженных из файлов конфигурации. Я только что создал класс case со всеми необходимыми конфигурационными переменными и установил их при запуске приложения. Затем я передаю этот класс case в любую функцию, которая требует от него информации.
Однако это кажется довольно раздражающим, особенно когда этот класс settings case должен распространяться через множество функций. Есть ли лучший способ сделать это?
Комментарии:
1. Просто добавьте config в качестве неявного параметра для каждого метода, которому это необходимо. Вы также могли бы взглянуть на reader monad .
Ответ №1:
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
нет параметра метода, например, contrast 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
via 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