ЗИО Экологическое строительство - PullRequest
3 голосов
/ 24 октября 2019

Я начал экспериментировать с ZIO и пытался запустить следующую очень сложную программу:

import logger.{Logger, _}
import zio.console._
import zio.{system, _}

object MyApp extends App {

  override def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = {

    app
      .provideSome[Logger](_ => Slf4jLogger.create) //1 
      .fold(_ => 1, _ => 0)
  }

  val app: ZIO[Console with system.System with Logger, SecurityException, Unit] =
    for {
      _         <- info("This message from the logger") //2
      maybeUser <- system.env("USER")
      _         <- maybeUser match {
                      case Some(userName) => putStrLn(s"Hello ${userName}!")
                      case None => putStrLn("I don't know your name")
                    }
    } yield ()
}

(Slf4jLogger равен https://github.com/NeQuissimus/zio-slf4j)

Если я комментирую строки //1 и //2 все работает нормально. Но в приведенной выше форме я получаю ошибку несоответствия типов:

Error:(13, 45) type mismatch;
 found   : logger.Slf4jLogger
 required: zio.console.Console with zio.system.System with logger.Logger
      .provideSome[Logger](_ => Slf4jLogger.create)

Я не понимаю следующее:

  1. Программамне потребовался экземпляр Console и System в среде, но мне не нужно было .provide раньше, когда я не регистрировался. Почему? И зачем мне это вдруг понадобилось?

  2. Согласно скалярному документу, .provideSome Предоставляет * некоторую * среду, необходимую для запуска этого эффекта, оставляя остаток R0. Для меня это означает, что мне не нужно предоставлятьполный тип среды, я мог бы добавить требуемые модули один за другим - но, похоже, ожидается полный тип ZEnv, как и .provide - так какой смысл?

  3. Я не могу создать экземпляранонимный класс new Logger with Console.Live with system.System.Live, потому что у Slf4jLogger есть фабрикаметод в сопутствующем объекте. Как я могу использовать этот фабричный метод с другими чертами?

  4. Поскольку создание экземпляра регистратора в Java довольно эффективно, является ли .provide даже подходящей функцией для вызова?

PS: я смог заставить это работать неким уродливым способом:

    app
      .provideSome[ZEnv](_ =>
        new Console with Logger with system.System {
          override val console = Console.Live.console
          override val system = System.Live.system
          override val logger = Slf4jLogger.create.logger
        }
      ) //1
      .fold(_ => 1, _ => 0)

Это компилирует и запускает, но переопределение внутренних элементов смешанных черт не даеткажется правильным ...

И он работает точно так же с .provide:

    app
      .provide(
        new Console with Logger with system.System {
          override val console = Console.Live.console
          override val system = System.Live.system
          override val logger = Slf4jLogger.create.logger
        }
      ) //1
      .fold(_ => 1, _ => 0)

Что происходит?

Ответы [ 3 ]

2 голосов
/ 25 октября 2019

Боюсь, вам нужно вернуть экземпляр, который реализует все характеристики, которые вы хотите предоставить.

Если вы посмотрите на пример в документации ZIO для provideSome, вы увидите, что они делаютточно так же: они берут Console и превращают его в Console with Logging.

/**
   * Provides some of the environment required to run this effect,
   * leaving the remainder `R0`.
   *
   * {{{
   * val effect: ZIO[Console with Logging, Nothing, Unit] = ???
   *
   * effect.provideSome[Console](console =>
   *   new Console with Logging {
   *     val console = console
   *     val logging = new Logging {
   *       def log(line: String) = console.putStrLn(line)
   *     }
   *   }
   * )
   * }}}
   */

Для меня это означает, что мне не нужно предоставлять полный тип среды, яможет добавить необходимые модули один за другим

Нет. Вы должны предоставить экземпляр полного типа среды для эффекта, который вы переносите (потому что это необходимо). Я думаю, что это потому, что мы хотим, чтобы это проверялось во время компиляции, и нет способа (без макросов) генерировать стекированную среду с «затиранием» без объяснения, как это сделать. Вероятно, в будущем будут какие-то макросы для этого.

точно так же, как .provide - так какой смысл?

Разница в том, что с .provide у вас естьсоздать этот экземпляр требуемой среды с нуля без какого-либо участия. Таким образом, в результате эффект (с вашей оболочкой) больше не требует ничего.

Принимая во внимание, что с .provideSome вы сами можете запросить среду, которая поможет вам создать экземпляр, который вы предоставляете. Эта среда будет передана в вашу функцию. В приведенном выше примере они требуют Console и увеличивают его до Console with Logging (используя предоставленный им экземпляр console). В результате для effect.provideSome[Console](...) по-прежнему требуется Console (тогда как для записи .provide потребуется Nothing).

.provideSome[ZEnv](_ =>
        new Console with Logger with system.System {
          override val console = Console.Live.console

Поскольку вы используете provideSome[ZEnv], вы, вероятно, не должны получать доступглобальные синглеты Console.Live, но возьмите это из env, передаваемого в:

.provideSome[ZEnv](env =>
        new Console with Logger with system.System {
          override val console = env.console

Для программы требовался экземпляр Console и System в среде, но мне не нужно было.provide до

Это связано с тем, что ZEnv среда по умолчанию уже предоставляет эти два.

Поскольку создание экземпляра регистратора в Java довольно эффективно,Является ли .provide даже правильной функцией для вызова?

Лично я бы проигнорировал, что создание логгеров эффективно. Это не похоже на то, что стоит отслеживать. Я считаю некоторым базовым средством, что это почти часть загрузки классов.

Если вы хотите отследить эффект, вы можете сделать

app
  .provideSomeM(env => for {
     logger <- UIO(Slf4jLogger.create)
   } yield new MyEnvironment(env, logger)
  )
1 голос
/ 24 октября 2019

Позвольте мне попытаться объяснить.

Если вы посмотрите, что ZEnv это псевдоним для Clock with Console with System with Random with Blocking. Итак, когда вы начали внедрять def run(args: List[String]): ZIO[ZEnv, Nothing, Int], zio уже предоставил вам эти возможности.

Ваша подпись переменной app имеет тип возврата zio со средой Console with System with Logger - одна вещь, которая выделяется, это возможность Logger. Это не стандартно, поэтому ZIO не может предоставить это вам. Вы должны предоставить его сами.

Это то, что вы сделали, используя .provideSome[Logger] - вы исключили одну из возможностей из требований среды, сделав тип совместимым со стандартом:

type ZEnv = Clock with Console with System with Random with Blocking
type YourEnv = Console with System
type Proof = ZEnv <:< YourEnv
0 голосов
/ 30 октября 2019

Я использую этот способ:

import logger.{Logger, _}
import zio.console._
import zio.{system, _}

object MyApp extends App {

  override def run(args: List[String]): ZIO[ZEnv, Nothing, Int] = {

    app
      .fold(_ => 1, _ => 0)
  }

  val app: ZIO[Environment, SecurityException, Unit] =
    for {
      _         <- info("This message from the logger").provide( Slf4jLogger.create) // 1
      maybeUser <- system.env("USER")
      _         <- maybeUser match {
                      case Some(userName) => putStrLn(s"Hello ${userName}!")
                      case None => putStrLn("I don't know your name")
                    }
    } yield ()
}
  1. Я предоставляю его прямо в for-comprehension приложения. Все остальное у вас есть от zio.App.
...