diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml new file mode 100644 index 000000000..b8883f62a --- /dev/null +++ b/.github/workflows/readme.yml @@ -0,0 +1,18 @@ +name: readme +on: [push, pull_request] +jobs: + readme: + runs-on: ubuntu-20.04 + timeout-minutes: 5 + env: + JAVA_OPTS: -Xms20G -Xmx20G -Xss10M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 + JVM_OPTS: -Xms20G -Xmx20G -Xss10M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 + steps: + - uses: actions/checkout@v3.0.2 + with: + fetch-depth: 0 + - uses: olafurpg/setup-scala@v13 + with: + java-version: openjdk@1.17.0 + - name: build + run: sbt "checkReadme" diff --git a/README.md b/README.md index 2692100a2..a77b2e38f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,514 @@ ![Chat](https://img.shields.io/discord/1087005439859904574) ![Version](https://img.shields.io/maven-central/v/io.getkyo/kyo-core_3) -Sorry, documentation coming soon + +Kyo is a complete toolkit for Scala application development, spanning from browser-based apps in ScalaJS to high-performance backends on the JVM. It introduces a novel approach based on algebraic effects to deliver straightforward APIs in the pure Functional Programming paradigm. Unlike similar solutions, Kyo achieves this without inundating developers with esoteric concepts from Category Theory, making for a development experience that's as intuitive as it is robust. + +Drawing inspiration from ZIO's effect rotation, Kyo takes a more generalized approach. While ZIO restricts effects to two channels, dependency injection and short-circuiting, Kyo allows for an arbitrary number of effectful channels. This enhancement affords developers greater flexibility in effect management, while also simplifying Kyo's internal codebase through more principled design patterns. + +## The `>` type + +In Kyo, computations computations are expressed via the infix type `>`, which takes two parameters: + +1. The first parameter specifies the type of the expected output. +2. The second parameter lists the pending effects that must be handled, represented as an **unordered** type-level set via a type interssection. + +```scala +import kyo._ + +// Expect an Int after handling the 'Options' effect +Int > Options + +// Expect a String after handling both 'Options' and 'IOs' effects +String > (Options with IOs) +``` + +> Note: effect types follow a naming convention, which is the plural form of the functionalities they manage. + +Kyo is designed so that any type `T` is automatically a `T > Any`, where `Any` signifies an empty set of pending effects. This design makes it straightforward to express computations without pending effects. + +```scala +import kyo._ + +// An 'Int' is also an 'Int > Any' +val a: Int > Any = 1 + +// Since there are no pending effects, the computation can produce a pure value +val b: Int = a.pure +``` + +It's possible to directly extract the pure value from a computation marked as `T > Any`. The given example essentially signifies a computation that yields an `Int` without any pending effects. Therefore, it's possible to safely extract the value. + +This property eliminates the need to distinguish between `map` and `flatMap`, as values are automatically lifted to a Kyo computation with no pending effects. + +```scala +import kyo.options._ +import kyo.tries._ + +// Kyo still supports both `map` and `flatMap` +def example1(a: Int > Options, b: Int > Tries): Int > (Options with Tries) = + a.flatMap(v => b.map(_ + v)) + +// But `map` alone suffices due to Kyo's design +def example2(a: Int > Options, b: Int > Tries): Int > (Options with Tries) = + a.map(v => b.map(_ + v)) +``` + +The `map` method in Kyo has the ability to automatically update the set of pending effects. When you apply `map` to computations that have different sets of pending effects, Kyo reconciles these into a new computation type that combines all the unique pending effects from both operands. + +## Effect widening + +Kyo's set of pending effects is a contravariant type parameter. This encoding permits computations to be widened to encompass a larger set of effects. + +```scala +// An 'Int' with an empty effect set (`Any`) +val a: Int > Any = 1 + +// Widening the effect set from empty (`Any`) to include `Options` +val b: Int > Options = a + +// Further widening the effect set to include both `Options` and `Tries` +val c: Int > (Options with Tries) = b + +// Directly widening a pure value to have `Options` and `Tries` +val d: Int > (Options with Tries) = 42 +``` + +This contravariant encoding enables a fluent API for effectful code, allowing methods to accept parameters with a specific set of pending effects while also permitting those with fewer or no effects: + +```scala +// The function expects a parameter with both 'Options' and 'Tries' effects pending +def example1(v: Int > (Options with Tries)) = + v.map(_ + 1) + +// A value with only the 'Tries' effect can be automatically widened to include 'Options' +def example2(v: Int > Tries) = example1(v) + +// A pure value can also be automatically widened +def example3 = example1(42) +``` + +Here, `example1` is designed to accept an `Int > (Options with Tries)`. However, thanks to the contravariant encoding of the type-level set of effects, `example2` and `example3` demonstrate that you can also pass in computations with a smaller set of effects—or even a pure value—and they will be automatically widened to fit the expected type. + +## Using effects + +Kyo offers a modular approach to effect management, accommodating both built-in and custom effects through an organized system of object modules. This organization ensures a consistent API and allows developers to focus on building complex applications without worrying about effect management intricacies. + +Importing the corresponding object module into scope brings in the effect and any additional implicits it may need. The naming convention uses lowercase object modules for each effect type. + +```scala +// for 'Options' effect +import kyo.options._ +// for 'Tries' effect +import kyo.tries._ +``` + +For effects that support it, a `get` method is provided, which permits the extraction of the underlying value from a computation. + +```scala +// Retrieves an 'Int' tagged with 'Options' +val a: Int > Options = Options.get(Some(1)) +``` + +Effect handling is done using the `run` method. Though it's named `run`, the method doesn't necessarily execute the computation immediately, as the effect handling operation can also be suspended if another effect is pending. + +```scala +// Handles 'Options' effect +val b: Option[Int] > Any = Options.run(a) + +// Retrieves pure value as there are no more pending effects +val c: Option[Int] = b.pure +``` + +The order in which you handle effects in Kyo can significantly influence both the type and value of the result. Since effects are unordered at the type level, the runtime behavior depends on the sequence in which effects are processed. + +```scala +import scala.util._ + +def optionsFirst(a: Int > (Options with Tries)): Try[Option[Int]] = { + val b: Option[Int] > Tries = Options.run(a) + val c: Try[Option[Int]] > Any = Tries.run(b) + c.pure +} +def triesFirst(a: Int > (Options with Tries)): Option[Try[Int]] = { + val b: Try[Int] > Options = Tries.run(a) + val c: Option[Try[Int]] > Any = Options.run(b) + c.pure +} +``` + +In this example, the order in which effects are handled significantly influences the outcome, particularly when the effects have the ability to short-circuit the computation: + +```scala +val ex = new Exception + +// if the effects don't short-circuit, only the order of nested types in the result changes +assert(optionsFirst(Options.get(Some(1))) == Success(Some(1))) +assert(optionsFirst(Tries.get(Success(1))) == Success(Some(1))) + +// note how the result type changes from 'Try[Option[T]]' to 'Option[Try[T]]' +assert(triesFirst(Options.get(Some(1))) == Some(Success(1))) +assert(triesFirst(Tries.get(Success(1))) == Some(Success(1))) + +// if there's short-circuiting, the resulting value can be different +assert(optionsFirst(Options.get(None)) == Success(None)) +assert(optionsFirst(Tries.get(Failure(ex))) == Failure(ex)) + +assert(triesFirst(Options.get(None)) == None) +assert(triesFirst(Tries.get(Failure(ex))) == Some(Failure(ex))) +``` + +## Core Effects + +### Aborts: Short Circuiting + +The `Aborts` effect is a generic implementation for short-circuiting effects. It's equivalent to ZIO's failure channel. + +```scala +import kyo.aborts._ + +// 'get' allows to "extract" the value from an 'Either' +val a: Int > Aborts[String] = Aborts[String].get(Right(1)) + +// short-circuiting via 'Left' +val b: Int > Aborts[String] = Aborts[String].get(Left("failed!")) + +// short-circuiting via 'Fail' +val c: Int > Aborts[String] = Aborts[String].fail("failed!") + +// 'catching' automatically catches exceptions +val d: Int > Aborts[Exception] = Aborts[Exception].catching(throw new Exception) +``` + +> Note that `Aborts` effect has a type parameter and its methods can only be accessed if the type parameter is provided. + +### IOs: Side Effects + +As you might have noticed, Kyo is unlike traditional effect systems since its base type doesn't assume that the computation might perform side effects. The `IOs` effect is introduced whenever a side effect needs to be performed. + +```scala +import kyo.ios._ + +// 'apply' is used to suspend side effects +val a: Int > IOs = IOs(Random.nextInt) + +// 'value' is a shorthand to widen a pure value to IOs +val b: Int > IOs = IOs.value(42) + +// 'fail' is returns a computation that will fail once IOs is handled +val c: Int > IOs = IOs.fail(new Exception) +``` + +> Note: Kyo's effects and public APIs are designed so any side effect is properly suspended via `IOs`, providing safe building blocks for pure computations. + +Users shouldn't typically handle the `IOs` effect directly since it triggers the execution of side effects, which breaks referential transparency. Prefer `kyo.App` instead. + +In some specific cases where Kyo isn't used as the main effect system of an application, it might make sense for the user to handle the `IOs` effect directly. The `run` method can only be used if `IOs`` is the only pending effect. + +```scala +val a: Int > IOs = IOs(42) +val b: Int = IOs.run(a).pure +``` + +The `runLazy` method accepts computations with other effects but it doesn't garantee that all side effects are performed before the method returns. If other effects still have to be handled, the side effects can be executed later once the other effects are run. This a low-level API that must be used with caution. + +```scala +// computation with an 'Options' and then an 'IOs' suspensions +val a: Int > (Options with IOs) = + Options.get(Some(42)).map { v => + IOs { + println(v) + v + } + } + +// handle the 'IOs' effect lazily +val b: Int > Options = IOs.runLazy(a) + +// since the computation is suspended withe 'Options' effect first, +// the lazy IOs execution will be triggered once 'Options' is handled +val c: Option[Int] = Options.run(b).pure +``` + +> IMPORTANT: Avoid handling the `IOs` effect directly since it breaks referential transparency. + +### Envs: Dependency Injection + +The `Envs` effect is similar to ZIO's environment mechanism but with a more flexible scoping since values can be provided individually. `Envs` doesn't provide a solution like ZIO's layers, though. The user is responsible from initializing environment values like services in parallel for example. + +```scala +import kyo.envs._ + +// Given an interface +trait Database { + def count: Int > IOs +} + +// The 'Envs' effect can be used to summon an instance. +// Note how the computation produces a 'Database' but at the +// same time requires a 'Database' from its environment +val a: Database > Envs[Database] = Envs[Database].get + +// use the 'Database' to obtain the count +val b: Int > (Envs[Database] with IOs) = a.map(_.count) + +// a 'Database' mock implementation +val db = new Database { + def count = 1 +} + +// handle the 'Envs' effect with the mock database +val c: Int > IOs = Envs[Database].run(db)(b) +``` + +A computation can also require multiple values from its environment. + +```scala +// a second interface to be injected +trait Cache { + def clear: Unit > IOs +} + +// a computation that requires two values +val a: Unit > (Envs[Database] with Envs[Cache] with IOs) = + Envs[Database].get.map { db => + db.count.map { + case 0 => Envs[Cache].get.map(_.clear) + case _ => () + } + } +``` + +### Locals: Scoped Values + +The `Locals` effect operates on top of `IOs` and enables the definition of scoped values. This mechanism is typically used to store contextual information of a computation. For example, in request processing, locals can be used to store the user who performed the request. In a library for database access, locals can be used to propagate transactions. + +To use `Locals`, first create a new `Local` instance with a default value. + +```scala +import kyo.locals._ + +// locals need to be initialized with a default value +val myLocal: Local[Int] = Locals.init(42) + +// the 'get' method returns the current value of the local +val a: Int > IOs = myLocal.get + +// the 'let' method assigns a value to a local within the +// scope of a computation. This code produces 43 (42 + 1) +val b: Int > IOs = + myLocal.let(42)(a.map(_ + 1)) +``` + +> Note: Kyo's effects are designed so locals are always properly propagated. For example, they're automatically inherited by forked computations in `Fibers`. + +### Resources: Resource Safety + +The `Resources` effect handles the safe use of external resources like network connections, files, and any other resource that needs to be freed once the computation finalizes. It's a mechanism similar to ZIO's `Scope`. + +```scala +import kyo.resources._ +import java.io.Closeable + +class Database extends Closeable { + def count: Int > IOs = 42 + def close() = {} +} + +// The `acquire` method accepts any object that implements Java's +// `Closeable` interface +val db: Database > (Resources with IOs) = + Resources.acquire(new Database) + +// Use `run` to handle the effect and close the resources +// utilized by a computation +val b: Int > IOs = Resources.run(db.map(_.count)) +``` + +The `ensure` method is a more low-level API to allow users to handle the finalization of resources directly. The `acquire` method is implemented in terms of `ensure`. + +```scala +// Example method to execute a function on a database +def withDb[T](f: Database => T > IOs): T > (IOs with Resources) = + // Initialize the database ('new Database' is a placeholder) + IOs(new Database).map { db => + // Registers `db.close` to be finalized once the `run` is called + Resources.ensure(db.close).map { _ => + // Invokes the function + f(db) + } + } + +// execute a function +val a: Int > (IOs with Resources) = + withDb(_.count) + +// close resources +val b: Int > IOs = Resources.run(a) +``` + +### Aspects: Aspect-Oriented Programming (AOP) + +The `Aspects` effects provide a mechanism for users to customize the behavior of a computation. Aspects in Kyo are expressed as first-class values, which enables flexible scoping. For example, users may instantiate aspects and reduce their visibility via regular field modifiers. + +To instantate an aspect, use the `Aspects.init` method. It takes three type parameters: + +1. `T`: The input type of the aspect +2. `U`: The output type of the aspect +3. `S`: The effects the aspect may perform + +```scala +import kyo.aspects._ + +// initialize an aspect that takes a 'Database' and returns +// an 'Int', potentially including 'IOs' +val countAspect: Aspect[Database, Int, IOs] = + Aspects.init[Database, Int, IOs] + +// the method 'apply' activates the aspect for a computation +def count(db: Database): Int > (Aspects with IOs) = + countAspect(db)(_.count) + +// to bind an aspect to an implementation, first create a new 'Cut' +val countPlusOne = + new Cut[Database, Int, IOs] { + // the first param is the input of the computation and the second is + // the computation being handled + def apply[S](v: Database > S)(f: Database => Int > (Aspects with IOs)) = + v.map(db => f(db).map(_ + 1)) + } + +// then bind the implementation to a computation with 'let' +// the first param is the 'Cut' and the second is the computation +// that will run with the custom binding of the aspect +def example(db: Database): Int > (Aspects with IOs) = + countAspect.let(countPlusOne) { + count(db) + } +``` + +If an aspect is bind to multiple `Cut` implementations, the order in which they're executed follows the order they're scoped in the computation. + +```scala +// another 'Cut' implementation +val countTimesTen = + new Cut[Database, Int, IOs] { + def apply[S](v: Database > S)(f: Database => Int > (Aspects with IOs)) = + v.map(db => f(db).map(_ * 10)) + } + +// first bind 'countPlusOne' then 'countTimesTen' +// the result will be (db.count + 1) * 10 +def example1(db: Database) = + countAspect.let(countPlusOne) { + countAspect.let(countTimesTen) { + count(db) + } + } + +// first bind 'countTimesTen' then 'countPlusOne' +// the result will be (db.count * 10) + 1 +def example2(db: Database) = + countAspect.let(countTimesTen) { + countAspect.let(countPlusOne) { + count(db) + } + } + +// cuts andcan also be composed via `andThen` +def example3(db: Database) = + countAspect.let(countTimesTen.andThen(countPlusOne)) { + count(db) + } +``` + +### Options: Optional Values + +```scala +import kyo.options._ + +// 'get' to "extract" the value of an 'Option' +val a: Int > Options = Options.get(Some(1)) + +// 'apply' is the effectful version of 'Option.apply' +val b: Int > Options = Options(1) + +// if 'apply' receives a 'null', it's equivalent to 'Options.get(None)' +assert(Options.run(Options(null)) == Options.run(Options.get(None))) + +// effectful version of `Option.getOrElse` +val c: Int > Options = Options.getOrElse(None, 42) + +// effectful verion of 'Option.orElse' +val d: Int > Options = Options.getOrElse(Some(1), c) +``` + +### Tries: Exception Handling + +```scala +import kyo.tries._ + +// 'get' to "extract" the value of an 'Try' +val a: Int > Tries = Tries.get(Try(1)) + +// 'fail' to short-circuit the computation +val b: Int > Tries = Tries.fail(new Exception) + +// 'fail' has an overload that takes an error message +val c: Int > Tries = Tries.fail("failed") + +// 'apply' is the effectful version of 'Try.apply' +val d: Int > Tries = Tries(1) + +// 'apply' automatically catches exceptions. Equivalent to 'Tries.fail(new Exception)' +val e: Int > Tries = Tries(throw new Exception) +``` + +### Consoles: interaction with the console + +```scala +import kyo.consoles._ + +// reads a line from the console +val a: String > Consoles = Consoles.readln + +// prints to the stdout +val b: Unit > Consoles = Consoles.print("ok") + +// prints to the stdout with a new line +val c: Unit > Consoles = Consoles.println("ok") + +// prints to the stderr +val d: Unit > Consoles = Consoles.printErr("fail") + +// prints to the stderr with a new line +val e: Unit > Consoles = Consoles.printlnErr("fail") + +// runs with the default implicit 'Console' implementation +val f: Unit > IOs = Consoles.run(e) + +// explictily setting the 'Console' implementation +val g: Unit > IOs = Consoles.run(Console.default)(e) +``` + +> Note how `Consoles.run` returns a computation with the `IOs` effect pending, which ensures the implementation of `Consoles` is pure. + +### Clocks: Time Management + +```scala +import kyo.clocks._ +import java.time.Instant + +// obtain the current time +val a: Instant > Clocks = Clocks.now + +// run with default 'Clock' +val b: Instant > IOs = Clocks.run(a) + +// run with an explicit 'Clock' +val c: Instant > IOs = Clocks.run(Clock.default)(a) +``` + License ------- diff --git a/build.sbt b/build.sbt index 3013fc29b..aed64a04a 100644 --- a/build.sbt +++ b/build.sbt @@ -232,6 +232,38 @@ lazy val `kyo-bench` = // libraryDependencies += "com.softwaremill.ox" %% "core" % "0.0.12" ) +lazy val rewriteReadmeFile = taskKey[Unit]("Rewrite README file") + +addCommandAlias("checkReadme", ";readme/rewriteReadmeFile; readme/mdoc") + +lazy val readme = + crossProject(JVMPlatform) + .withoutSuffixFor(JVMPlatform) + .crossType(CrossType.Pure) // new documentation project + .in(file("target/readme")) // important: it must not be docs/ + .enablePlugins(MdocPlugin) + .settings( + `kyo-settings`, + `without-cross-scala`, + mdocIn := new File("./../../README-in.md"), + mdocOut := new File("./../../README-out.md"), + rewriteReadmeFile := { + val readmeFile = new File("README.md") + val targetReadmeFile = new File("target/README-in.md") + val contents = IO.read(readmeFile) + val newContents = contents.replaceAll("```scala\n", "```scala mdoc:nest\n") + IO.write(targetReadmeFile, newContents) + } + ) + .dependsOn( + `kyo-core`, + `kyo-zio`, + `kyo-direct`, + `kyo-sttp`, + `kyo-chatgpt`, + `kyo-bench` + ) + lazy val `js-settings` = Seq( Compile / doc / sources := Seq.empty, fork := false diff --git a/kyo-core/shared/src/main/scala/kyo/KyoApp.scala b/kyo-core/shared/src/main/scala/kyo/KyoApp.scala index ef99e80f0..ee12c4fea 100644 --- a/kyo-core/shared/src/main/scala/kyo/KyoApp.scala +++ b/kyo-core/shared/src/main/scala/kyo/KyoApp.scala @@ -38,7 +38,7 @@ object KyoApp { val v6: T > (IOs with Fibers) = Timers.run(v5) val v7: T > (IOs with Fibers with Timers) = Fibers.timeout(timeout)(v6) val v8: T > (IOs with Fibers) = Timers.run(v6) - val v9: Fiber[T] > IOs = Fibers.run(IOs.lazyRun(v8)) + val v9: Fiber[T] > IOs = Fibers.run(IOs.runLazy(v8)) IOs.run(v9) } } diff --git a/kyo-core/shared/src/main/scala/kyo/ios.scala b/kyo-core/shared/src/main/scala/kyo/ios.scala index 1be08b2b5..1de8d1657 100644 --- a/kyo-core/shared/src/main/scala/kyo/ios.scala +++ b/kyo-core/shared/src/main/scala/kyo/ios.scala @@ -78,22 +78,22 @@ object ios { } /*inline*/ - def lazyRun[T, S](v: T > (IOs with S)): T > S = { - def lazyRunLoop(v: T > (IOs with S)): T > S = { + def runLazy[T, S](v: T > (IOs with S)): T > S = { + def runLazyLoop(v: T > (IOs with S)): T > S = { val safepoint = Safepoint.noop[IO, IOs] v match { case kyo: Kyo[IO, IOs, Unit, T, S with IOs] @unchecked if (kyo.effect eq IOs) => - lazyRunLoop(kyo((), safepoint, Locals.State.empty)) + runLazyLoop(kyo((), safepoint, Locals.State.empty)) case kyo: Kyo[MX, EX, Any, T, S with IOs] @unchecked => new KyoCont[MX, EX, Any, T, S](kyo) { def apply(v: Any, s: Safepoint[MX, EX], l: Locals.State) = - lazyRunLoop(kyo(v, s, l)) + runLazyLoop(kyo(v, s, l)) } case _ => v.asInstanceOf[T] } } - lazyRunLoop(v) + runLazyLoop(v) } private[kyo] def ensure[T, S](f: => Unit > IOs)(v: => T > S): T > (IOs with S) = { diff --git a/kyo-core/shared/src/test/scala/kyoTest/concurrent/fibersTest.scala b/kyo-core/shared/src/test/scala/kyoTest/concurrent/fibersTest.scala index 84b72710a..443be7bf2 100644 --- a/kyo-core/shared/src/test/scala/kyoTest/concurrent/fibersTest.scala +++ b/kyo-core/shared/src/test/scala/kyoTest/concurrent/fibersTest.scala @@ -296,7 +296,7 @@ class fibersTest extends KyoTest { for { l <- Latches.init(1) - fiber <- Fibers.run(IOs.lazyRun(Fibers.fork(task(l)))) + fiber <- Fibers.run(IOs.runLazy(Fibers.fork(task(l)))) _ <- Fibers.sleep(10.millis) interrupted <- fiber.interrupt _ <- l.await diff --git a/kyo-core/shared/src/test/scala/kyoTest/iosTest.scala b/kyo-core/shared/src/test/scala/kyoTest/iosTest.scala index bcd179483..c4adf8079 100644 --- a/kyo-core/shared/src/test/scala/kyoTest/iosTest.scala +++ b/kyo-core/shared/src/test/scala/kyoTest/iosTest.scala @@ -25,7 +25,7 @@ class iosTest extends KyoTest { } assert(!called) checkEquals[Int, Nothing]( - IOs.lazyRun(v), + IOs.runLazy(v), 1 ) assert(called) @@ -40,7 +40,7 @@ class iosTest extends KyoTest { } } assert(!called) - val v2 = IOs.lazyRun(v) + val v2 = IOs.runLazy(v) assert(!called) checkEquals[Int, Nothing]( Envs[Int].run(1)(v2), @@ -59,7 +59,7 @@ class iosTest extends KyoTest { IOs(IOs(1)).map(_ => fail) ) ios.foreach { io => - assert(Try(IOs.lazyRun(io)) == Try(fail)) + assert(Try(IOs.runLazy(io)) == Try(fail)) } succeed } @@ -73,7 +73,7 @@ class iosTest extends KyoTest { i } checkEquals[Int, Nothing]( - IOs.lazyRun(loop(0)), + IOs.runLazy(loop(0)), frames ) } diff --git a/kyo-core/shared/src/test/scala/kyoTest/resourcesTest.scala b/kyo-core/shared/src/test/scala/kyoTest/resourcesTest.scala index 4504a2299..5e42321ae 100644 --- a/kyo-core/shared/src/test/scala/kyoTest/resourcesTest.scala +++ b/kyo-core/shared/src/test/scala/kyoTest/resourcesTest.scala @@ -47,7 +47,7 @@ class resourcesTest extends KyoTest { val r1 = Resource(1) val r2 = Resource(2) val r = - IOs.lazyRun { + IOs.runLazy { Resources.run[Int, IOs with Envs[Int]](Resources.acquire(r1()).map { _ => assert(r1.closes == 0) Envs[Int].get @@ -101,7 +101,7 @@ class resourcesTest extends KyoTest { val r1 = Resource(1) val r2 = Resource(2) val r: Int > Envs[Int] = - IOs.lazyRun { + IOs.runLazy { Resources.run[Int, IOs with Envs[Int]] { val io: Int > (Resources with IOs with Envs[Int]) = for { diff --git a/kyo-zio/src/main/scala/kyo/KyoZioApp.scala b/kyo-zio/src/main/scala/kyo/KyoZioApp.scala index e8740fa3b..b9c98c696 100644 --- a/kyo-zio/src/main/scala/kyo/KyoZioApp.scala +++ b/kyo-zio/src/main/scala/kyo/KyoZioApp.scala @@ -56,7 +56,7 @@ object KyoZioApp { val v5: T > (IOs with Fibers with ZIOs) = Timers.run(v4) val v6: T > (IOs with ZIOs) = inject[T, Fiber, Task, Fibers, ZIOs, IOs](Fibers, ZIOs)(v5) - val v7: T > ZIOs = IOs.lazyRun(v6) + val v7: T > ZIOs = IOs.runLazy(v6) ZIOs.run(v7) } } diff --git a/project/plugins.sbt b/project/plugins.sbt index 8b7c8cfe1..5cc98ab45 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,3 +4,5 @@ addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.2") + +addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.7" ) \ No newline at end of file