ZIO环境建设

时间:2019-10-24 08:04:36

标签: scala zio

我开始尝试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. 根据scaladoc的说法,.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 个答案:

答案 0 :(得分:2)

恐怕您必须返回一个实例,该实例实现您要提供的所有特征。

如果您查看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 :(得分:1)

让我尝试解释。

如果您查看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

答案 2 :(得分:0)

这是我使用的一种方式:

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中的所有剩余内容。