Шаблон tagless-final позволяет нам писать чисто функциональные программы, в которых подробно рассказывается о необходимых им эффектах.
Однако масштабирование этого шаблона может стать сложной задачей.Я постараюсь продемонстрировать это на примере.Представьте себе простую программу, которая читает записи из базы данных и выводит их на консоль.Нам потребуются некоторые пользовательские классы типов Database
и Console
, в дополнение к Monad
от cats / scalaz для их составления:
def main[F[_]: Monad: Console: Database]: F[Unit] =
read[F].flatMap(Console[F].print)
def read[F[_]: Functor: Database]: F[List[String]] =
Database[F].read.map(_.map(recordToString))
Проблема начинается, когда я хочу добавить новыйэффект на функцию во внутренних слоях.Например, я хочу, чтобы моя функция read
регистрировала сообщение, если записи не были найдены
def read[F[_]: Monad: Database: Logger]: F[List[String]] =
Database[F].read.flatMap {
case Nil => Logger[F].log("no records found") *> Nil.pure
case records => records.map(recordToString).pure
}
Но теперь мне нужно добавить ограничение Logger
для всех вызывающих read
доцепь.В этом надуманном примере это просто main
, но представьте, что это несколько уровней сложного реального приложения.
Мы можем рассмотреть эту проблему двумя способами:
- МыМожно сказать, что это хорошо, что было ясно о наших эффектах, и мы точно знаем, какие эффекты нужны каждому слою
- Мы также можем сказать, что это приводит к утечке деталей реализации -
main
не заботится о регистрации,ему просто нужен результат read
.Кроме того, в реальных приложениях вы видите действительно длинные цепочки эффектов в верхних слоях.Это похоже на запах кода, но я не могу понять, какой другой подход я могу использовать.
Хотелось бы узнать ваше мнение об этом.
Спасибо.