├── .gitignore ├── .jvmopts ├── LICENSE.md ├── README.md ├── build.sbt ├── cats-effect-loom └── src │ ├── main │ └── scala │ │ └── cps │ │ └── monads │ │ └── catsEffect │ │ └── CpsCERuntimeAwait.scala │ └── test │ └── scala │ └── cps │ └── celoom │ ├── LoomHOFunSuite.scala │ └── MyList.scala ├── cats-effect ├── jvm │ └── src │ │ └── test │ │ ├── resources │ │ └── input │ │ └── scala │ │ └── cps │ │ └── cats │ │ └── effect │ │ ├── AsyncFileChannelResourceSuite.scala │ │ ├── AsyncFileChannelResourceSuite2.scala │ │ ├── AsyncFileChannelResourceSuite3.scala │ │ ├── AsyncFileChannelResourceSuiteMin.scala │ │ └── FileChannelResourceSuite.scala └── shared │ └── src │ ├── main │ └── scala │ │ └── cps │ │ ├── catsEffect │ │ └── DirectRef.scala │ │ └── monads │ │ └── catsEffect │ │ ├── CatsAsync.scala │ │ ├── CatsIO.scala │ │ ├── ResourceCpsMonad.scala │ │ ├── Using.scala │ │ └── syntax │ │ └── monadless │ │ └── package.scala │ └── test │ └── scala │ ├── cps │ └── catsEffect │ │ ├── ApiExampleDJSSuite.scala │ │ ├── CatsEffectBackDoor.scala │ │ ├── DCAIssue65Suite.scala │ │ ├── FutureInteropSuite.scala │ │ ├── LazyEffectSuite.scala │ │ ├── ResourceMonadSuite.scala │ │ ├── ResourceSuite.scala │ │ ├── StupidFizzBuzzSuite.scala │ │ ├── ToyLogger.scala │ │ └── TryFinallyCancellableSuite.scala │ └── midgard │ ├── HelloWorld.scala │ └── HelloWorldSuite.scala ├── monix ├── js │ └── src │ │ └── test │ │ └── scala │ │ └── cps │ │ └── monixtest │ │ └── SingleThreadScheduler.scala ├── jvm │ └── src │ │ └── test │ │ └── scala │ │ └── cps │ │ └── monixtest │ │ └── SingleThreadScheduler.scala └── shared │ └── src │ ├── main │ └── scala │ │ └── cps │ │ ├── monads │ │ └── monix │ │ │ └── MonixCpsMonad.scala │ │ └── stream │ │ └── monix │ │ └── ObservableEmitAbsorber.scala │ └── test │ └── scala │ └── cps │ └── monixtest │ ├── BasicGeneratorSuite.scala │ ├── ConcurrentSuite.scala │ ├── FutureInteropSuite.scala │ ├── LazyEffectSuite.scala │ ├── StupidFizzBuzzSuite.scala │ └── ToyLogger.scala ├── probability-monad └── src │ ├── main │ └── scala │ │ ├── cps │ │ └── monads │ │ │ └── probability │ │ │ └── DistributionCpsMonad.scala │ │ └── probability_monad │ │ └── TryDistributions.scala │ └── test │ └── scala │ └── cps │ └── monads │ └── probability │ └── ProbabilityExamplesSuite.scala ├── project ├── build.properties └── plugins.sbt ├── publish.sbt ├── scalaz └── shared │ └── src │ ├── main │ └── scala │ │ └── cps │ │ └── monads │ │ └── scalaz │ │ └── ScalazIOCpsMonad.scala │ └── test │ └── scala │ └── cps │ └── monads │ └── scalaz │ └── IOPrintSuite.scala ├── stream-akka └── src │ ├── main │ └── scala │ │ └── cps │ │ └── stream │ │ └── akka │ │ └── AkkaStreamEmitAbsorber.scala │ └── test │ └── scala │ └── cps │ └── akkastreamtest │ └── BasicGeneratorSuite.scala ├── stream-fs2 └── shared │ └── src │ ├── main │ └── scala │ │ └── cpsfs2 │ │ └── Fs2AsyncEmitter.scala │ └── test │ └── scala │ └── cpsfs2test │ └── BasicGeneratorSuite.scala ├── stream-pekko └── src │ ├── main │ └── scala │ │ └── cps │ │ └── stream │ │ └── pekko │ │ └── PekkoStreamEmitAbsorber.scala │ └── test │ └── scala │ └── cps │ └── pekkostreamtest │ └── BasicGeneratorSuite.scala ├── zio └── shared │ └── src │ ├── main │ └── scala │ │ └── cps │ │ ├── monads │ │ └── zio │ │ │ ├── ThrowableAdapter.scala │ │ │ ├── Using.scala │ │ │ ├── ZIOCpsMonad.scala │ │ │ └── ZManagedCpsMonad.scala │ │ └── stream │ │ └── zio │ │ └── ZStreamEmitAbsorber.scala │ └── test │ └── scala │ └── cpszio │ ├── BasicGeneratorSuite.scala │ ├── ConcurrentSuite.scala │ ├── FutureInteropSuite.scala │ ├── LazyEffectSuite.scala │ ├── ResourceSuite.scala │ ├── StupidFizzBuzzSuite.scala │ ├── TLogging.scala │ └── TestAsyncZIO.scala ├── zio2-loom └── src │ ├── main │ └── scala │ │ └── cps │ │ └── monads │ │ └── zio │ │ └── ZIO2RuntimeAwaitProvider.scala │ └── test │ └── scala │ └── cps │ └── zioloomtest │ ├── LoomHOFunSuite.scala │ └── MyList.scala └── zio2 └── shared └── src ├── main └── scala │ └── cps │ ├── monads │ └── zio │ │ ├── ThrowableAdapter.scala │ │ ├── Using.scala │ │ ├── ZIOCpsMonad.scala │ │ └── ZManagedCpsMonad.scala │ └── stream │ └── zio │ └── ZStreamEmitAbsorber.scala └── test └── scala └── cpszio ├── BasicGeneratorSuite.scala ├── ConcurrentSuite.scala ├── DCAIssue65Suite.scala ├── FutureInteropSuite.scala ├── Issue4Example.scala ├── LazyEffectSuite.scala ├── ResourceSuite.scala ├── StupidFizzBuzzSuite.scala ├── TLogging.scala └── TestAsyncZIO.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /.jvmopts: -------------------------------------------------------------------------------- 1 | -Xmx4G 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is a helper connect objects for providing [dotty-cps-async](https://github.com/rssh/dotty-cps-async) CpsAsyncMonad typeclasses for common effect stacks and streaming libraries. 4 | 5 | 6 | ## cats-effects: 7 | 8 | ``` 9 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-cats-effect" % version 10 | ``` 11 | 12 | And if you want to use JDK-21 virtual threads for translation of high-order function arguments: 13 | 14 | ``` 15 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-cats-effect-loom" % version 16 | ``` 17 | 18 | If you use scala lts versinm then use artefacts with lts suffix: 19 | 20 | ``` 21 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-cats-effect-lts" % version 22 | ``` 23 | 24 | Usage: 25 | 26 | ```scala 27 | import cps._ 28 | import cps.monads.catsEffect.given 29 | 30 | ... 31 | def doSomething(): IO[T] = async[IO] { 32 | ... 33 | } 34 | 35 | ``` 36 | 37 | or import specific class to allow compiler to deduce given monad automatically. 38 | 39 | * IO - catsIO (implements CpsConcurrentEffectMonad with conversion to Future) 40 | * Generic `F[_]:Async` - catsAsync (implements CpsAsyncMonad) 41 | * Generic `F[_]:MonadThrow` - catsMonadThrow (implements CpsTryMonad) 42 | * Generic `F[_]:MonadCancel` - catsMonadCancel (implements CpsTryMonad) 43 | * Generic `F[_]:Monad` - catsMonad (implements CpsMonad) 44 | 45 | Also implemented pseudo-synchronious interface for resources, i.e. for `r:Resource[F,A]` it is possible to write: 46 | 47 | ```scala 48 | async[F] { 49 | ....... 50 | Resource.using(r1,r2){ file1, file2 => 51 | val data = await(fetchData(url)) 52 | file1.write(data) 53 | file1.write(s"data fetched from $url") 54 | } 55 | } 56 | ``` 57 | 58 | or 59 | 60 | ```scala 61 | async[F] { 62 | .... 63 | r.useOn{file => 64 | val data = await(fetchData()) 65 | file.write(data) 66 | } 67 | } 68 | ``` 69 | 70 | instead 71 | 72 | ``` 73 | await(r.use{ 74 | fetchData().map(data => f.write(data)) 75 | }) 76 | ``` 77 | 78 | ## Cancellation 79 | 80 | `cps-async-connect-cats-effect` provides `CpsMonadCancel` typeclass, which implements finalising of resources during cancellation. 81 | 82 | I.e. if we have next code: 83 | 84 | ```scala 85 | async[IO] { 86 | val r = allocateResource() 87 | try { 88 | await(doSomething(r)) 89 | } catch { 90 | case NonFatal(e) => 91 | handleException(e) 92 | } finally { 93 | r.release() 94 | } 95 | } 96 | ``` 97 | and Fiber which evaluated this code will be canceled during `doSomething`, then finally block will be executed. 98 | 99 | Note, that execution model in this case will be differ from traditional java try-catch-finally: 100 | * it is impossible to catch CancellationException in the catch block. 101 | * during cancellation, exception from finalizers will not be propagated as outcome of cancelled Fiber, but instead 102 | will be reported to the exception handler of the IO execution pool. 103 | * when finalizer is executed not during cancellation, but during normal completion, then exception will be propagated as outcome of the Fiber. 104 | 105 | 109 | 110 | 111 | # monix: 112 | 113 | ``` 114 | libraryDependencies += "com.github.rssh" %%% "cps-async-connect-monix" % version 115 | ``` 116 | 117 | (or with '-lts' for scala-lts version) 118 | 119 | Usage: 120 | 121 | ```scala 122 | import cps.* 123 | import cps.monads.monix.given 124 | import monix.eval.Task 125 | 126 | ... 127 | def doSomething(): Task[T] = async[Task] { 128 | ... 129 | } 130 | 131 | ``` 132 | 133 | ```scala 134 | import cps.* 135 | import monix.* 136 | import monix.reactive.* 137 | import cps.monads.monix.given 138 | import cps.stream.monix.given 139 | 140 | def intStream() = asyncStream[Observable[Int]] { out => 141 | for(i <- 1 to N) { 142 | out.emit(i) 143 | } 144 | } 145 | 146 | ``` 147 | 148 | 149 | ## scalaz IO: 150 | 151 | ``` 152 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-scalaz" % version 153 | ``` 154 | 155 | * IO - cps.monads.scalaz.scalazIO (implements CpsTryMonad) 156 | 157 | 158 | ## zio: 159 | 160 | for 1.x: 161 | 162 | ``` 163 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-zio" % version 164 | ``` 165 | 166 | for 2.x: 167 | 168 | ``` 169 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-zio2" % version 170 | ``` 171 | 172 | and for loom support on JDK21+ : 173 | 174 | ``` 175 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-zio2-loom" % version 176 | ``` 177 | 178 | 179 | 180 | Usage: 181 | 182 | ```scala 183 | import cps.* 184 | import cps.monads.zio.{given,*} 185 | 186 | val program = asyncRIO[R] { 187 | ..... 188 | } 189 | 190 | ``` 191 | 192 | or for task: 193 | 194 | ```scala 195 | 196 | val program = async[Task] { 197 | .... 198 | } 199 | 200 | 201 | ``` 202 | 203 | 204 | * ZIO - `asyncZIO[R,E]` as shortcat for `async[[X]=>>ZIO[R,E,X]]` (implements `CpsAsyncMonad` with conversion to `Future` if we have given `Runtime` in scope.) 205 | * RIO - use asyncRIO[R] (implements CpsAsyncMonad with conversion) 206 | * Task - use async[Task] (implements CpsAsyncMonad with conversion) 207 | * URIO - use asyncURIO[R] (implements CpsMonad) 208 | 209 | Also implement `using` pseudo-syntax for ZManaged: 210 | 211 | ``` 212 | asyncRIO[R] { 213 | val managedResource = Managed.make(Queue.unbounded[Int])(_.shutdown) 214 | ZManaged.using(managedResource, secondResource) { queue => 215 | doSomething() // can use awaits inside. 216 | } 217 | 218 | } 219 | ``` 220 | 221 | And generator syntax for ZStream: 222 | 223 | ``` 224 | val stream = asyncStream[Stream[Throwable,Int]] { out => 225 | for(i <- 1 to N) { 226 | out.emit(i) 227 | } 228 | } 229 | ``` 230 | 231 | 232 | ## akka-streams 233 | 234 | 235 | ``` 236 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-akka-stream" % version 237 | ``` 238 | 239 | Generator syntax for akka source. 240 | 241 | 242 | ## fs2 streams 243 | 244 | ``` 245 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-fs2" % version 246 | ``` 247 | 248 | Generator syntax for fs2 249 | 250 | ## pekko-streams 251 | 252 | ``` 253 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-pekko-stream" % version 254 | ``` 255 | 256 | Generator syntax for pekko source. 257 | 258 | 259 | ## probability monad 260 | 261 | ``` 262 | libraryDependencies += "io.github.dotty-cps-async" %%% "cps-async-connect-probability-monad" % version 263 | ``` 264 | 265 | CpsTryMonad instance for Distribution monad. 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import sbt.Keys.autoCompilerPlugins 2 | 3 | 4 | //val dottyVersion = "3.4.0-RC1-bin-SNAPSHOT" 5 | val dottyVersion = "3.3.6" 6 | val dottyCpsAsyncVersion = "1.0.3" 7 | 8 | ThisBuild/version := "1.0.3-SNAPSHOT" 9 | ThisBuild/versionScheme := Some("semver-spec") 10 | ThisBuild/organization := "io.github.dotty-cps-async" 11 | ThisBuild/resolvers += Resolver.mavenLocal 12 | ThisBuild/publishTo := localStaging.value 13 | 14 | Global / concurrentRestrictions += Tags.limit(ScalaJSTags.Link, 1) 15 | Global / concurrentRestrictions += Tags.limit(ScalaJSTags.Link, 1) 16 | 17 | lazy val commonSettings = Seq( 18 | scalaVersion := dottyVersion, 19 | libraryDependencies += "io.github.dotty-cps-async" %%% "dotty-cps-async" % dottyCpsAsyncVersion, 20 | libraryDependencies += "org.scalameta" %%% "munit" % "1.0.4" % Test, 21 | testFrameworks += new TestFramework("munit.Framework"), 22 | scalacOptions ++= Seq( "-Wvalue-discard", "-Wnonunit-statement"), 23 | autoCompilerPlugins := true, 24 | addCompilerPlugin("io.github.dotty-cps-async" %% "dotty-cps-async-compiler-plugin" % dottyCpsAsyncVersion) 25 | ) 26 | 27 | 28 | lazy val scalaz = crossProject(JSPlatform, JVMPlatform) 29 | .in(file("scalaz")) 30 | .settings( 31 | commonSettings, 32 | name := "cps-async-connect-scalaz", 33 | libraryDependencies += "org.scalaz" %%% "scalaz-effect" % "7.4.0-M14" , 34 | libraryDependencies += "org.scalaz" %%% "scalaz-core" % "7.4.0-M14" 35 | ).jsSettings( 36 | scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, 37 | scalaJSUseMainModuleInitializer := true 38 | ) 39 | 40 | 41 | lazy val catsEffect = crossProject(JSPlatform, JVMPlatform) 42 | .in(file("cats-effect")) 43 | .settings( 44 | commonSettings, 45 | name := "cps-async-connect-cats-effect", 46 | libraryDependencies += "org.typelevel" %%% "cats-effect" % "3.6.1", 47 | libraryDependencies += "org.typelevel" %%% "munit-cats-effect" % "2.1.0" % Test 48 | ).jsSettings( 49 | scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, 50 | scalaJSUseMainModuleInitializer := true, 51 | ).jvmSettings( 52 | scalacOptions ++= Seq( "-unchecked", "-explain") 53 | ) 54 | 55 | lazy val catsEffectLoom = project.in(file("cats-effect-loom")) 56 | .dependsOn(catsEffect.jvm) 57 | .settings( 58 | commonSettings, 59 | name := "cps-async-connect-cats-effect-loom", 60 | libraryDependencies ++= Seq( 61 | "io.github.dotty-cps-async" %% "dotty-cps-async-loom" % dottyCpsAsyncVersion, 62 | "org.typelevel" %%% "munit-cats-effect" % "2.1.0" % Test 63 | ), 64 | scalacOptions += "-Xtarget:21" 65 | ) 66 | 67 | 68 | lazy val monix = crossProject(JSPlatform, JVMPlatform) 69 | .in(file("monix")) 70 | .settings( 71 | commonSettings, 72 | name := "cps-async-connect-monix", 73 | libraryDependencies += "io.monix" %%% "monix" % "3.4.1", 74 | ).jsSettings( 75 | scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, 76 | scalaJSUseMainModuleInitializer := true 77 | ).jvmSettings( 78 | ) 79 | 80 | lazy val zio = crossProject(JSPlatform, JVMPlatform) 81 | .in(file("zio")) 82 | .settings( 83 | commonSettings, 84 | name := "cps-async-connect-zio", 85 | libraryDependencies ++= Seq( 86 | "dev.zio" %%% "zio" % "1.0.18", 87 | "dev.zio" %%% "zio-streams" % "1.0.18", 88 | ) 89 | ).jsSettings( 90 | scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, 91 | //scalaJSUseMainModuleInitializer := true, 92 | libraryDependencies ++= Seq( 93 | "io.github.cquiroz" %%% "scala-java-time" % "2.6.0", 94 | "io.github.cquiroz" %%% "scala-java-time-tzdb" % "2.6.0" 95 | ), 96 | ).jvmSettings( 97 | scalacOptions ++= Seq( "-unchecked", "-Ydebug-trace", "-Ydebug-names", "-Xprint-types", 98 | "-Ydebug", "-uniqid", "-Ycheck:macros", "-Yprint-syms" ) 99 | ) 100 | 101 | lazy val zio2 = crossProject(JSPlatform,JVMPlatform) 102 | .in(file("zio2")) 103 | .settings( 104 | commonSettings, 105 | name := "cps-async-connect-zio2", 106 | libraryDependencies ++= Seq( 107 | "dev.zio" %%% "zio" % "2.1.17", 108 | "dev.zio" %%% "zio-managed" % "2.1.17", 109 | "dev.zio" %%% "zio-streams" % "2.1.17", 110 | ) 111 | ).jsSettings( 112 | scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, 113 | //scalaJSUseMainModuleInitializer := true, 114 | libraryDependencies ++= Seq( 115 | "io.github.cquiroz" %%% "scala-java-time" % "2.6.0", 116 | "io.github.cquiroz" %%% "scala-java-time-tzdb" % "2.6.0" 117 | ), 118 | ).jvmSettings( 119 | scalacOptions ++= Seq( "-unchecked", "-Ydebug-trace", "-Ydebug-names", "-Xprint-types", 120 | "-Ydebug", "-uniqid", "-Ycheck:macros", "-Yprint-syms" 121 | ) 122 | ) 123 | 124 | lazy val zio2Loom = project.in(file("zio2-loom")) 125 | .dependsOn(zio2.jvm) 126 | .settings( 127 | commonSettings, 128 | name := "cps-async-connect-zio2-loom", 129 | libraryDependencies ++= Seq( 130 | "io.github.dotty-cps-async" %% "dotty-cps-async-loom" % dottyCpsAsyncVersion 131 | ), 132 | scalacOptions += "-Xtarget:21" 133 | ) 134 | 135 | 136 | lazy val streamFs2 = crossProject(JSPlatform, JVMPlatform) 137 | .in(file("stream-fs2")) 138 | .dependsOn(catsEffect) 139 | .settings( 140 | commonSettings, 141 | name := "cps-async-connect-fs2", 142 | libraryDependencies ++= Seq( 143 | "co.fs2" %%% "fs2-core" % "3.12.0", 144 | "org.typelevel" %%% "munit-cats-effect" % "2.1.0" % Test 145 | ) 146 | ) 147 | 148 | lazy val streamAkka = (project in file("stream-akka")). 149 | settings( 150 | commonSettings, 151 | name := "cps-async-connect-akka-stream", 152 | scalacOptions += "-explain", 153 | resolvers += "Akka library repository".at("https://repo.akka.io/maven"), 154 | libraryDependencies ++= Seq( 155 | ("com.typesafe.akka" %% "akka-stream" % "2.10.5") 156 | ) 157 | ) 158 | 159 | lazy val streamPekko = (project in file("stream-pekko")). 160 | settings( 161 | commonSettings, 162 | name := "cps-async-connect-pekko-stream", 163 | scalacOptions += "-explain", 164 | libraryDependencies ++= Seq( 165 | ("org.apache.pekko" %% "pekko-stream" % "1.1.3") 166 | ) 167 | ) 168 | 169 | 170 | lazy val probabilityMonad = (project in file("probability-monad")). 171 | settings( 172 | commonSettings, 173 | name := "cps-async-connect-probabiliy-monad", 174 | libraryDependencies ++= Seq( 175 | ("org.jliszka" %%% "probability-monad" % "1.0.4").cross(CrossVersion.for3Use2_13) 176 | ) 177 | ) 178 | 179 | 180 | lazy val cpsAsyncConnect = (project in file(".")) 181 | .aggregate(catsEffect.jvm, catsEffect.js, 182 | catsEffectLoom, 183 | monix.jvm, monix.js, 184 | scalaz.jvm, scalaz.js , 185 | zio.jvm, zio.js, 186 | zio2.jvm, zio2.js, 187 | zio2Loom, 188 | streamFs2.jvm, streamFs2.js, 189 | streamAkka, 190 | streamPekko, 191 | probabilityMonad 192 | ) 193 | 194 | 195 | -------------------------------------------------------------------------------- /cats-effect-loom/src/main/scala/cps/monads/catsEffect/CpsCERuntimeAwait.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.catsEffect 2 | 3 | import cps.* 4 | import cats.effect.* 5 | import cats.effect.std.Dispatcher 6 | 7 | import scala.concurrent.{ExecutionException, blocking} 8 | import java.util.concurrent.CompletableFuture 9 | import scala.util.control.NonFatal 10 | 11 | class CpsCERuntimeAwait[F[_]](dispatcher: Dispatcher[F], async: Async[F]) extends CpsRuntimeAwait[F] { 12 | 13 | override def await[A](fa: F[A])(ctx: CpsTryMonadContext[F]): A = { 14 | import scala.concurrent.ExecutionContext.Implicits.global 15 | val cf = CompletableFuture[A]() 16 | dispatcher.unsafeToFuture(fa).onComplete{ 17 | case scala.util.Success(a) => cf.complete(a) 18 | case scala.util.Failure(ex) => cf.completeExceptionally(ex) 19 | } 20 | blocking { 21 | try { 22 | cf.get() 23 | } catch { 24 | case ex: ExecutionException => 25 | throw ex.getCause() 26 | } 27 | } 28 | } 29 | 30 | } 31 | 32 | class CpsCERuntimeAwaitProvider[F[_]:Async] extends CpsRuntimeAwaitProvider[F] { 33 | 34 | def runInVirtualThread[A](op: =>A): F[A] = 35 | Async[F].async_{ cb => 36 | Thread.ofVirtual().start(() => { 37 | try { 38 | val r = op 39 | cb(Right(r)) 40 | }catch{ 41 | case ec: ExecutionException => 42 | cb(Left(ec.getCause())) 43 | case NonFatal(ex) => 44 | cb(Left(ex)) 45 | } 46 | }) 47 | } 48 | 49 | override def withRuntimeAwait[A](op: CpsRuntimeAwait[F] => F[A])(using ctx:CpsTryMonadContext[F]): F[A] = 50 | Dispatcher.sequential[F].use{dispatcher => 51 | val ra = CpsCERuntimeAwait(dispatcher, summon[Async[F]]) 52 | ctx.monad.flatten(runInVirtualThread(op(ra))) 53 | } 54 | 55 | } 56 | 57 | given cpsCERuntimeAwaitProvider[F[_]](using Async[F]): CpsRuntimeAwaitProvider[F] = 58 | CpsCERuntimeAwaitProvider[F] 59 | -------------------------------------------------------------------------------- /cats-effect-loom/src/test/scala/cps/celoom/LoomHOFunSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.celoom 2 | 3 | import cats.effect.* 4 | import cats.effect.kernel.* 5 | 6 | import cps.* 7 | import cps.monads.catsEffect.{given,*} 8 | 9 | import munit.CatsEffectSuite 10 | 11 | 12 | class LoomHOFunSuite extends CatsEffectSuite { 13 | 14 | 15 | test("check apply function with await as argument to MyList.map") { 16 | val c=async[IO] { 17 | val list0 = MyList.create(1,2,3,4,5) 18 | val list1 = list0.map(x => await(IO(x+1))) 19 | assert (list1 == MyList.create(2,3,4,5,6)) 20 | } 21 | c 22 | } 23 | 24 | test("check apply function with await as argument to MyList.foldLeft") { 25 | val c=async[IO] { 26 | val list0 = MyList.create(1,2,3,4,5) 27 | val sum = list0.foldLeft(0)((s,x) => await(IO(s+x))) 28 | assert (sum == 15) 29 | } 30 | c 31 | } 32 | 33 | def twice[A](f: A=>A)(a:A):A = f(f(a)) 34 | 35 | test("check await in the argument of twice") { 36 | val c = async[IO] { 37 | val r = twice[Int](x => await(IO(x+1)))(1) 38 | assert(r == 3) 39 | } 40 | c 41 | } 42 | 43 | test("check await in the argument of twice inside MyList.map") { 44 | val c = async[IO] { 45 | val list0 = MyList.create(1,2,3,4,5) 46 | val list1 = list0.map(x => twice[Int](x => await(IO(x+1)))(x)) 47 | assert(list1 == MyList.create(3,4,5,6,7)) 48 | } 49 | c 50 | } 51 | 52 | test("catch exception from failed operation inside runtime await") { 53 | val c = async[IO] { 54 | val list0 = MyList.create(1, 2, 3, 4, 5) 55 | try { 56 | val list1 = list0.map[Int](x => await(IO.raiseError(new RuntimeException("test")))) 57 | assert(false) 58 | } catch { 59 | case ex: RuntimeException => 60 | assert(ex.getMessage() == "test") 61 | } 62 | } 63 | c 64 | } 65 | 66 | def topNScored[A](c:Iterable[A], n: Int)(score: A=>Double): Seq[(A,Double)] = { 67 | c.map(x => (x, score(x))).toSeq.sortBy(- _._2).take(n) 68 | } 69 | 70 | def score(x:Int):IO[Double] = { 71 | IO.delay(Math.floor((1.0/x) * 1000)) 72 | } 73 | 74 | test("select top 3 scored") { 75 | val c = async[IO] { 76 | val list0 = List(1,2,3,4,5,6,7) 77 | val list1 = topNScored(list0, 3)(x => await(score(x))) 78 | println(s"list1 = ${list1}") 79 | assert(list1 == Seq((1,1000.0), (2,500.0), (3,333.0))) 80 | } 81 | c 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /cats-effect-loom/src/test/scala/cps/celoom/MyList.scala: -------------------------------------------------------------------------------- 1 | package cps.celoom 2 | 3 | 4 | sealed trait MyList[+A] { 5 | 6 | def map[B](f: A=>B): MyList[B] 7 | 8 | def foldLeft[B](z: B)(f: (B, A) => B): B = this match { 9 | case MyNil => z 10 | case MyCons(hd, tl) => tl.foldLeft(f(z, hd))(f) 11 | } 12 | 13 | def length: Int 14 | 15 | } 16 | 17 | case object MyNil extends MyList[Nothing] { 18 | 19 | override def map[B](f: Nothing => B): MyList[B] = MyNil 20 | 21 | override def length: Int = 0 22 | 23 | } 24 | 25 | case class MyCons[T](hd: T, tl: MyList[T]) extends MyList[T] { 26 | 27 | override def map[B](f: T => B): MyList[B] = MyCons(f(hd), tl.map(f)) 28 | 29 | override def length: Int = 1 + tl.length 30 | 31 | } 32 | 33 | object MyList { 34 | 35 | def create[T](args: T*):MyList[T] = { 36 | if (args.isEmpty) MyNil 37 | else MyCons(args.head, create(args.tail*)) 38 | } 39 | 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /cats-effect/jvm/src/test/resources/input: -------------------------------------------------------------------------------- 1 | AAA AC 2 | -------------------------------------------------------------------------------- /cats-effect/jvm/src/test/scala/cps/cats/effect/AsyncFileChannelResourceSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.cats.effecct 2 | 3 | import scala.concurrent.* 4 | import scala.concurrent.duration.* 5 | import scala.util.* 6 | import scala.collection.mutable.ArrayBuffer 7 | 8 | import cats.effect.* 9 | import cats.effect.kernel.* 10 | 11 | import cps.* 12 | import cps.monads.{given,*} 13 | import cps.monads.catsEffect.{given,*} 14 | import cps.syntax.* 15 | 16 | import munit.CatsEffectSuite 17 | 18 | import java.nio.ByteBuffer 19 | import java.nio.channels.AsynchronousFileChannel 20 | import java.nio.channels.CompletionHandler 21 | import java.nio.file.Path 22 | import java.nio.file.Paths 23 | import java.nio.file.Files 24 | import java.nio.file.{OpenOption, StandardOpenOption} 25 | 26 | 27 | 28 | 29 | def openAsyncFileChannel(name: Path, options: OpenOption*): Resource[IO, AsynchronousFileChannel] = 30 | Resource.make(acquire = IO.delay(AsynchronousFileChannel.open(name,options:_*)))(release = f=>IO(f.close())) 31 | 32 | 33 | def read(input: AsynchronousFileChannel, buffer: ByteBuffer): IO[Int] = 34 | IO.async_[Int]{ cb => 35 | input.read(buffer, 0, (), new CompletionHandler[Integer, Unit]() { 36 | def completed(result: Integer, attachment: Unit) = 37 | val res: Int = result 38 | cb(Right(res)) 39 | def failed(ex: Throwable, attachment: Unit) = 40 | cb(Left(ex)) 41 | }) 42 | } 43 | 44 | 45 | def write(output: AsynchronousFileChannel, buffer: ByteBuffer): IO[Int] = 46 | IO.async_[Int]{ cb => 47 | output.write(buffer, 0, (), new CompletionHandler[Integer, Unit]() { 48 | def completed(result: Integer, attachment: Unit) = 49 | val res: Int = result 50 | cb(Right(res)) 51 | 52 | def failed(ex: Throwable, attachment: Unit) = 53 | cb(Left(ex)) 54 | }) 55 | } 56 | 57 | 58 | class AsyncFileChannelResourceSuite extends CatsEffectSuite { 59 | 60 | val BUF_SIZE = 64000 61 | 62 | test("use cats resource with AsynchronousFileChannel") { 63 | import StandardOpenOption.* 64 | //implicit val printCode = cps.macros.flags.PrintCode 65 | //implicit val debugLavel = cps.macros.flags.DebugLevel(1) 66 | val prg = asyncScope[IO] { 67 | val input = ! openAsyncFileChannel(Paths.get("cats-effect/jvm/src/test/resources/input"),READ) 68 | val outputName = Files.createTempFile("output-async",null) 69 | val output = ! openAsyncFileChannel(outputName,WRITE, CREATE, TRUNCATE_EXISTING) 70 | val buffer = ByteBuffer.allocate(BUF_SIZE) 71 | var nBytes = 0 72 | var cBytes = 0 73 | while 74 | cBytes = ! read(input, buffer.clear()) 75 | if (cBytes > 0) then 76 | !write(output, buffer) 77 | nBytes += cBytes 78 | cBytes == BUF_SIZE 79 | do () 80 | (nBytes, outputName) 81 | } 82 | prg.map{ 83 | (n, name) => 84 | //println(s"n = $n") 85 | Files.delete(name) 86 | //println(s"messages = $messages") 87 | assert(n > 0) 88 | } 89 | 90 | } 91 | 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /cats-effect/jvm/src/test/scala/cps/cats/effect/AsyncFileChannelResourceSuite2.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffecct 2 | 3 | import scala.concurrent.* 4 | import scala.concurrent.duration.* 5 | import scala.util.* 6 | import scala.collection.mutable.ArrayBuffer 7 | 8 | import cats.effect.* 9 | import cats.effect.kernel.* 10 | 11 | import cps.* 12 | import cps.monads.{given,*} 13 | import cps.monads.catsEffect.{given,*} 14 | 15 | import munit.CatsEffectSuite 16 | 17 | import java.nio.ByteBuffer 18 | import java.nio.channels.AsynchronousFileChannel 19 | import java.nio.channels.CompletionHandler 20 | import java.nio.file.Path 21 | import java.nio.file.Paths 22 | import java.nio.file.Files 23 | import java.nio.file.{OpenOption, StandardOpenOption} 24 | 25 | 26 | 27 | object AsyncChannelApi: 28 | 29 | def open(name: Path, options: OpenOption*): Resource[IO, AsynchronousFileChannel] = 30 | Resource.make(acquire = IO.delay(AsynchronousFileChannel.open(name,options:_*)))(release = f=>IO(f.close())) 31 | 32 | def read(input: AsynchronousFileChannel, bufSize: Int): IO[ByteBuffer] = 33 | IO.async_[ByteBuffer]{ cb => 34 | val buffer = ByteBuffer.allocate(bufSize) 35 | input.read(buffer, 0, (), new CompletionHandler[Integer, Unit]() { 36 | def completed(result: Integer, attachment: Unit) = 37 | cb(Right(buffer)) 38 | def failed(ex: Throwable, attachment: Unit) = 39 | cb(Left(ex)) 40 | }) 41 | } 42 | 43 | 44 | def write(output: AsynchronousFileChannel, buffer: ByteBuffer): IO[Int] = 45 | IO.async_[Int]{ cb => 46 | output.write(buffer, 0, (), new CompletionHandler[Integer, Unit]() { 47 | def completed(result: Integer, attachment: Unit) = 48 | val res: Int = result 49 | cb(Right(res)) 50 | 51 | def failed(ex: Throwable, attachment: Unit) = 52 | cb(Left(ex)) 53 | }) 54 | } 55 | 56 | import AsyncChannelApi.* 57 | 58 | 59 | class AsyncFileChannelResourceSuite2 extends CatsEffectSuite { 60 | 61 | val BUF_SIZE = 64000 62 | 63 | test("use cats resource with AsynchronousFileChannel2") { 64 | import StandardOpenOption.* 65 | //implicit val printCode = cps.macros.flags.PrintCode 66 | val prg = asyncScope[IO] { 67 | val input = await(open(Paths.get("cats-effect/jvm/src/test/resources/input"),READ)) 68 | val outputName = Files.createTempFile("output-async2",null) 69 | val output = await(open(outputName,WRITE, CREATE, TRUNCATE_EXISTING)) 70 | var nBytes = 0 71 | while 72 | val buffer = await(read(input, BUF_SIZE)) 73 | val cBytes = buffer.position() 74 | val _ = await(write(output, buffer)) 75 | nBytes += cBytes 76 | cBytes == BUF_SIZE 77 | do () 78 | (nBytes, outputName) 79 | } 80 | prg.map{ 81 | (n, name) => 82 | //println(s"n = $n") 83 | Files.delete(name) 84 | //println(s"messages = $messages") 85 | assert(n > 0) 86 | } 87 | 88 | } 89 | 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /cats-effect/jvm/src/test/scala/cps/cats/effect/AsyncFileChannelResourceSuite3.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffecct 2 | 3 | import scala.annotation.experimental 4 | import scala.concurrent.* 5 | import scala.concurrent.duration.* 6 | import scala.util.* 7 | import scala.collection.mutable.ArrayBuffer 8 | 9 | import cats.effect.* 10 | import cats.effect.kernel.* 11 | 12 | import cps.* 13 | import cps.monads.{given,*} 14 | import cps.monads.catsEffect.{given,*} 15 | 16 | import munit.CatsEffectSuite 17 | 18 | import java.nio.ByteBuffer 19 | import java.nio.channels.AsynchronousFileChannel 20 | import java.nio.channels.CompletionHandler 21 | import java.nio.file.Path 22 | import java.nio.file.Paths 23 | import java.nio.file.Files 24 | import java.nio.file.{OpenOption, StandardOpenOption} 25 | 26 | 27 | 28 | @experimental 29 | object AsyncChannelApi3: 30 | 31 | type IOResourceDirect = CpsDirect[[X]=>>Resource[IO,X]] 32 | 33 | def openM(name: Path, options: OpenOption*): Resource[IO, AsynchronousFileChannel] = 34 | Resource.make(acquire = IO.delay(AsynchronousFileChannel.open(name,options:_*)))(release = f=>IO(f.close())) 35 | 36 | def open(name: Path, options: OpenOption*)(using IOResourceDirect): AsynchronousFileChannel = 37 | await(openM(name,options: _*)) 38 | 39 | 40 | def readM(input: AsynchronousFileChannel, bufSize: Int): IO[ByteBuffer] = 41 | IO.async_[ByteBuffer]{ cb => 42 | val buffer = ByteBuffer.allocate(bufSize) 43 | input.read(buffer, 0, (), new CompletionHandler[Integer, Unit]() { 44 | def completed(result: Integer, attachment: Unit) = 45 | cb(Right(buffer)) 46 | def failed(ex: Throwable, attachment: Unit) = 47 | cb(Left(ex)) 48 | }) 49 | } 50 | 51 | def read(input: AsynchronousFileChannel, bufSize: Int)(using CpsDirect[IO]): ByteBuffer = 52 | await(readM(input, bufSize)) 53 | 54 | def writeM(output: AsynchronousFileChannel, buffer: ByteBuffer): IO[Int] = 55 | IO.async_[Int]{ cb => 56 | output.write(buffer, 0, (), new CompletionHandler[Integer, Unit]() { 57 | def completed(result: Integer, attachment: Unit) = 58 | val res: Int = result 59 | cb(Right(res)) 60 | 61 | def failed(ex: Throwable, attachment: Unit) = 62 | cb(Left(ex)) 63 | }) 64 | } 65 | 66 | 67 | def write(output: AsynchronousFileChannel, buffer: ByteBuffer)(using CpsDirect[IO]): Int = 68 | await(writeM(output, buffer)) 69 | 70 | 71 | 72 | 73 | 74 | 75 | @experimental 76 | class AsyncFileChannelResourceSuite3 extends CatsEffectSuite { 77 | 78 | import AsyncChannelApi3.* 79 | 80 | val BUF_SIZE = 64000 81 | 82 | test("use cats resource with AsynchronousFileChannel3") { 83 | import StandardOpenOption.* 84 | //implicit val printCode = cps.macros.flags.PrintCode 85 | val prg = asyncScope[IO] { 86 | val input = open(Paths.get("cats-effect/jvm/src/test/resources/input"),READ) 87 | val outputName = Files.createTempFile("output-async3",null) 88 | val output = open(outputName,WRITE, CREATE, TRUNCATE_EXISTING) 89 | var nBytes = 0 90 | while 91 | val buffer = read(input, BUF_SIZE) 92 | val cBytes = buffer.position() 93 | val wBytes = write(output, buffer) 94 | nBytes += cBytes 95 | cBytes == BUF_SIZE 96 | do () 97 | (nBytes, outputName) 98 | } 99 | prg.map{ 100 | (n, name) => 101 | //println(s"n = $n") 102 | Files.delete(name) 103 | //println(s"messages = $messages") 104 | assert(n > 0) 105 | } 106 | 107 | } 108 | 109 | } 110 | 111 | 112 | -------------------------------------------------------------------------------- /cats-effect/jvm/src/test/scala/cps/cats/effect/AsyncFileChannelResourceSuiteMin.scala: -------------------------------------------------------------------------------- 1 | package cps.cats.effecct 2 | 3 | import scala.concurrent.* 4 | import scala.concurrent.duration.* 5 | import scala.util.* 6 | import scala.collection.mutable.ArrayBuffer 7 | 8 | import cats.effect.* 9 | import cats.effect.kernel.* 10 | 11 | import cps.* 12 | import cps.monads.{given,*} 13 | import cps.monads.catsEffect.{given,*} 14 | 15 | import munit.CatsEffectSuite 16 | 17 | import java.nio.ByteBuffer 18 | import java.nio.channels.AsynchronousFileChannel 19 | import java.nio.channels.CompletionHandler 20 | import java.nio.file.Path 21 | import java.nio.file.Paths 22 | import java.nio.file.Files 23 | import java.nio.file.{OpenOption, StandardOpenOption} 24 | 25 | 26 | 27 | 28 | //def openAsyncFileChannelM(name: String, options: OpenOption*): Resource[IO, AsynchronousFileChannel] = 29 | def openAsyncFileChannelM(name: String, options: OpenOption*): Resource[IO, AsynchronousFileChannel] = 30 | throw RuntimeException("???") 31 | 32 | 33 | 34 | def writeM(output: AsynchronousFileChannel, buffer: Int): IO[Int] = 35 | throw RuntimeException("???") 36 | 37 | 38 | class AsyncFileChannelResourceSuiteMin extends CatsEffectSuite { 39 | 40 | val BUF_SIZE = 64000 41 | 42 | test("use cats resource with AsynchronousFileChannel - compile only") { 43 | import StandardOpenOption.* 44 | //implicit val printCode = cps.macros.flags.PrintCode 45 | //implicit val debugLavel = cps.macros.flags.DebugLevel(15) 46 | try { 47 | val prg = asyncScope[IO] { 48 | val output = await(openAsyncFileChannelM("outputName")) 49 | val _ = await(writeM(output, 1)) 50 | 1 51 | } 52 | prg.recover{case e => e.getMessage} 53 | } catch { 54 | case ex: RuntimeException => 55 | IO.pure(ex.getMessage) 56 | } 57 | } 58 | 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /cats-effect/jvm/src/test/scala/cps/cats/effect/FileChannelResourceSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.cats.effecct 2 | 3 | import scala.concurrent.duration._ 4 | import scala.collection.mutable.ArrayBuffer 5 | 6 | import cats.effect.* 7 | import cats.effect.kernel.* 8 | 9 | import cps.* 10 | import cps.monads.catsEffect.{given,*} 11 | 12 | import munit.CatsEffectSuite 13 | 14 | import java.nio.ByteBuffer 15 | import java.nio.channels.FileChannel 16 | import java.nio.file.Path 17 | import java.nio.file.Paths 18 | import java.nio.file.Files 19 | import java.nio.file.{OpenOption, StandardOpenOption} 20 | 21 | import cps.syntax.* 22 | 23 | 24 | def openFileChannel(name: Path, options: OpenOption*): Resource[IO, FileChannel] = 25 | Resource.make(acquire = IO.delay(FileChannel.open(name,options:_*)))(release = f=>IO(f.close())) 26 | 27 | 28 | 29 | class FileChannelResourceSuite extends CatsEffectSuite { 30 | 31 | test("use cats resource") { 32 | import StandardOpenOption.* 33 | val prg = asyncScope[IO] { 34 | val input = !openFileChannel(Paths.get("cats-effect/jvm/src/test/resources/input"),READ) 35 | val outputName = Files.createTempFile("output",null) 36 | val output = !openFileChannel(outputName,WRITE, CREATE, TRUNCATE_EXISTING) 37 | (output.transferFrom(input,0,Long.MaxValue), outputName) 38 | } 39 | prg.map{ 40 | (n, name) => 41 | //println(s"n = $n") 42 | Files.delete(name) 43 | //println(s"messages = $messages") 44 | assert(n > 0) 45 | } 46 | 47 | } 48 | 49 | } 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /cats-effect/shared/src/main/scala/cps/catsEffect/DirectRef.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import cats.* 4 | import cats.effect.* 5 | import cps.* 6 | 7 | import scala.annotation.experimental 8 | 9 | @experimental 10 | object directRefs { 11 | 12 | opaque type DirectRef[F[_],T] = Ref[F,T] 13 | 14 | object DirectRef { 15 | def apply[F[_],T](ref:Ref[IO,T]):DirectRef[IO,T] = ref 16 | } 17 | 18 | extension[F[_],A] (ref: DirectRef[F,A]) { 19 | 20 | def get(using CpsDirect[F]): A = 21 | await(ref.get) 22 | 23 | def set(using CpsDirect[F])(a: A): Unit = 24 | await(ref.set(a)) 25 | 26 | def update(using CpsDirect[F])(f: A => A): Unit = 27 | await(ref.update(f)) 28 | 29 | } 30 | 31 | extension (io: IO.type) { 32 | 33 | def directRefOf[T](t: T)(using cpsDirect: CpsDirect[IO]): DirectRef[IO, T] = 34 | DirectRef(await(Ref.of[IO, T](t))) 35 | 36 | } 37 | 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /cats-effect/shared/src/main/scala/cps/monads/catsEffect/CatsAsync.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.catsEffect 2 | 3 | import cps._ 4 | import cats.{Monad, MonadThrow} 5 | import cats.effect.kernel._ 6 | 7 | import scala.util._ 8 | import scala.util.control.NonFatal 9 | import scala.concurrent._ 10 | 11 | trait CatsMonad[F[_]](using mf: Monad[F]) extends CpsMonad[F]: 12 | 13 | def pure[A](a:A): F[A] = 14 | mf.pure(a) 15 | 16 | def map[A,B](fa: F[A])(f: A => B): F[B] = 17 | mf.map(fa)(f) 18 | 19 | def flatMap[A,B](fa: F[A])(f: A => F[B]): F[B] = 20 | mf.flatMap(fa)(f) 21 | 22 | 23 | class CatsMonadPure[F[_]](using mf: Monad[F]) extends CatsMonad[F](using mf) with CpsPureMonadInstanceContext[F] 24 | 25 | 26 | class CatsMonadThrow[F[_]](using MonadThrow[F]) extends CatsMonad[F] with CpsTryMonadInstanceContext[F]: 27 | 28 | def error[A](e: Throwable): F[A] = 29 | summon[MonadThrow[F]].raiseError(e) 30 | 31 | override def mapTry[A,B](fa:F[A])(f: Try[A]=>B): F[B] = 32 | summon[MonadThrow[F]].redeem(fa)( ex => f(Failure(ex)), a => f(Success(a)) ) 33 | 34 | def flatMapTry[A,B](fa:F[A])(f: Try[A]=>F[B]): F[B] = 35 | summon[MonadThrow[F]].redeemWith(fa)( ex => f(Failure(ex)), a => f(Success(a)) ) 36 | 37 | 38 | class CatsMonadCancel[F[_]](using F: MonadCancel[F, Throwable]) extends CatsMonadThrow[F] with CpsTryMonadInstanceContext[F]: 39 | 40 | // will be override when Sync become available 41 | def poorMansDelay[A](a: =>A) = F.map(F.unit)(_ => a) 42 | 43 | def poorMansFlatDelay[A](a: => F[A]) = F.flatMap(F.unit)(_ => a) 44 | 45 | override def withAction[A](fa: F[A])(action: => Unit): F[A] = { 46 | withAsyncAction(fa)(poorMansDelay(action)) 47 | } 48 | 49 | override def withAsyncAction[A](fa: F[A])(action: => F[Unit]): F[A] = { 50 | //F.guaranteeCase(fa)(_ => poorMansFlatDelay(action)) 51 | import cats.syntax.all.* 52 | F.uncancelable { poll => 53 | F.onCancel(poll(fa), poorMansFlatDelay(action) ).handleErrorWith { ex => 54 | action.handleErrorWith { ex1 => 55 | ex1.addSuppressed(ex) 56 | F.raiseError(ex1) 57 | }.flatMap(_ => F.raiseError(ex)) 58 | }.flatTap { _ => 59 | action 60 | } 61 | } 62 | } 63 | 64 | override def withAsyncFinalizer[A](fa: => F[A])(f: => F[Unit]): F[A] = { 65 | withAsyncAction(tryImpure(fa))(f) 66 | } 67 | 68 | 69 | 70 | object CatsAsyncHelper: 71 | 72 | def adoptCallbackStyle[F[_],A](source: (Try[A]=>Unit) => Unit)(using Async[F]): F[A] = { 73 | def adoptIOCallback(ioCallback: Either[Throwable, A] => Unit): Try[A] => Unit = { 74 | case Failure(ex) => ioCallback(Left(ex)) 75 | case Success(a) => ioCallback(Right(a)) 76 | } 77 | 78 | summon[Async[F]].async_ { 79 | ioCallback => source(adoptIOCallback(ioCallback)) 80 | } 81 | } 82 | 83 | end CatsAsyncHelper 84 | 85 | 86 | class CatsAsync[F[_]](using Async[F]) extends CatsMonadThrow[F] with CpsAsyncEffectMonadInstanceContext[F]: 87 | 88 | def adoptCallbackStyle[A](source: (Try[A]=>Unit) => Unit): F[A] = 89 | CatsAsyncHelper.adoptCallbackStyle[F,A](source) 90 | 91 | end CatsAsync 92 | 93 | trait CatsAsyncCancel[F[_]](using Async[F], MonadCancel[F, Throwable]) extends CatsMonadCancel[F] with CpsAsyncEffectMonadInstanceContext[F]: 94 | 95 | override def poorMansDelay[A](a: => A) = summon[Async[F]].delay(a) 96 | 97 | override def poorMansFlatDelay[A](a: => F[A]) = summon[Async[F]].defer(a) 98 | 99 | override def withAction[A](fa: F[A])(action: => Unit): F[A] = { 100 | withAsyncAction(fa)(summon[Async[F]].delay(action)) 101 | } 102 | 103 | override def withAsyncAction[A](fa: F[A])(action: => F[Unit]): F[A] = { 104 | import cats.syntax.all.* 105 | summon[MonadCancel[F, Throwable]].uncancelable { poll => 106 | summon[MonadCancel[F, Throwable]].onCancel(poll(fa), summon[Async[F]].defer(action)) 107 | .handleErrorWith { ex => 108 | action.handleErrorWith{ ex1 => 109 | ex1.addSuppressed(ex) 110 | summon[MonadCancel[F,Throwable]].raiseError(ex1) 111 | }.flatMap{ _ => 112 | summon[MonadCancel[F,Throwable]].raiseError(ex) 113 | } 114 | } 115 | .flatTap { _ => action } 116 | } 117 | } 118 | 119 | 120 | // inlined to avoid unoptimized virtual calls 121 | //override def withAction[A](fa: F[A])(action: => Unit): F[A] = { 122 | // summon[MonadCancel[F,Throwable]].guaranteeCase(fa) { _ => summon[Async[F]].delay(action) } 123 | //} 124 | 125 | // inlined to avoid unoptimized virtual calls 126 | //override def withAsyncAction[A](fa: F[A])(action: => F[Unit]): F[A] = { 127 | // summon[MonadCancel[F,Throwable]].guaranteeCase(fa)(_ => summon[Async[F]].defer(action)) 128 | //} 129 | 130 | def adoptCallbackStyle[A](source: (Try[A]=>Unit) => Unit): F[A] = 131 | CatsAsyncHelper.adoptCallbackStyle(source) 132 | 133 | 134 | end CatsAsyncCancel 135 | 136 | 137 | class CatsConcurrent[F[_]](using Concurrent[F], Async[F], MonadCancel[F, Throwable]) extends CatsAsyncCancel[F] 138 | with CpsConcurrentEffectMonadInstanceContext[F]: 139 | 140 | type Spawned[A] = Fiber[F,Throwable,A] 141 | 142 | def spawnEffect[A](op: => F[A]): F[Spawned[A]] = 143 | summon[Concurrent[F]].start{ 144 | try 145 | op 146 | catch 147 | case NonFatal(ex) => 148 | error(ex) 149 | } 150 | 151 | def join[A](op: Spawned[A]): F[A] = 152 | flatMap(op.join){ r => 153 | r match 154 | case Outcome.Succeeded(fa) => fa 155 | case Outcome.Errored(ex) => error(ex) 156 | case Outcome.Canceled() => error(new CancellationException("fiber is cancelled")) 157 | } 158 | 159 | def tryCancel[A](op: Spawned[A]): F[Unit] = 160 | op.cancel 161 | 162 | 163 | 164 | 165 | given catsMonadPure[F[_]](using Monad[F], NotGiven[MonadThrow[F]]): CpsPureMonadInstanceContext[F] = CatsMonadPure[F]() 166 | 167 | given catsMonadThrow[F[_]](using MonadThrow[F], NotGiven[Async[F]]): CpsTryMonadInstanceContext[F] = CatsMonadThrow[F]() 168 | 169 | given catsMonadCancel[F[_]](using MonadCancel[F, Throwable], NotGiven[Async[F]]): CatsMonadCancel[F] = CatsMonadCancel[F]() 170 | 171 | given catsAsync[F[_]](using Async[F], NotGiven[Concurrent[F]], NotGiven[MonadCancel[F,Throwable]]): CatsAsync[F] = CatsAsync[F]() 172 | 173 | given catsConcurrent[F[_]](using Concurrent[F], Async[F], MonadCancel[F,Throwable]): CpsConcurrentEffectMonadInstanceContext[F] = CatsConcurrent[F]() 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /cats-effect/shared/src/main/scala/cps/monads/catsEffect/CatsIO.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.catsEffect 2 | /* 3 | * (C) Ruslan Shevchenko 4 | * 2021 5 | */ 6 | 7 | import cats.effect._ 8 | import cps._ 9 | 10 | import scala.util._ 11 | import scala.concurrent._ 12 | 13 | /** 14 | * CpsAsyncMonad for cats-effect. 15 | **/ 16 | class CatsIOCpsAsyncMonad extends CatsConcurrent[IO]: 17 | 18 | type F[T] = IO[T] 19 | 20 | 21 | given catsIO: CatsIOCpsAsyncMonad = CatsIOCpsAsyncMonad() 22 | 23 | 24 | given ioToFutureConversion(using runtime: unsafe.IORuntime): CpsMonadConversion[IO,Future] with 25 | 26 | def apply[T](io:IO[T]): Future[T] = 27 | io.unsafeToFuture() 28 | 29 | 30 | -------------------------------------------------------------------------------- /cats-effect/shared/src/main/scala/cps/monads/catsEffect/ResourceCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.catsEffect 2 | /* 3 | * (C) Ruslan Shevchenko 4 | * 2021 - 2023 5 | */ 6 | 7 | import scala.util.{Try,Success,Failure} 8 | 9 | import cats.* 10 | import cats.effect.* 11 | import cats.effect.kernel.* 12 | 13 | import cps.* 14 | 15 | 16 | 17 | /** 18 | * into F[T] to [A] =>> Resource[F,A] for using inside asyncScope 19 | **/ 20 | given resourceConversion[F[_]]: CpsMonadConversion[F, [A] =>> Resource[F,A]] = 21 | new CpsMonadConversion[F, [A]=>>Resource[F,A]] { 22 | def apply[T](ft:F[T]):Resource[F,T] = Resource.eval(ft) 23 | } 24 | 25 | /** 26 | * part of asyncScope 27 | *@see asyncScope 28 | */ 29 | class AsyncScopeInferArg[F[_],C <: CpsMonadContext[[A]=>>Resource[F,A]]](using am: CpsTryMonad.Aux[[A]=>>Resource[F,A],C], mc: MonadCancel[F,Throwable]) { 30 | 31 | transparent inline def apply[T](inline body: C ?=> T):F[T] = 32 | am.apply(cps.macros.Async.transformContextLambda(body)).use(t=>summon[MonadCancel[F,Throwable]].pure(t)) 33 | 34 | } 35 | 36 | /** 37 | * Produce effect with resource-aware scope block. 38 | * 39 | * ``` 40 | * val effect = asyncScope[IO] { 41 | * val reader = await(openFile(input)) 42 | * val writer = await(openFile(output)) 43 | * writer.transformFrom(0,Long.MaxValue,reader) 44 | * } 45 | * ``` 46 | * Here evaluation of effect will open reader and wrier, transfer data and then close reader and writer. 47 | * block inside asyncScope evaluated in CpsResourceMonad[[X]=>>Resource[F,X]] 48 | *@see [cps.monads.catsEffect.CpsResourceMonad] 49 | */ 50 | def asyncScope[F[_]](using m:CpsTryMonad[[A]=>>Resource[F,A]], mc:MonadCancel[F,Throwable]) = AsyncScopeInferArg(using m, mc) 51 | 52 | /** 53 | * Synonym for `asyncScope`. 54 | **/ 55 | def reifyScope[F[_]](using m:CpsTryMonad[[A]=>>Resource[F,A]], mc:MonadCancel[F,Throwable]) = AsyncScopeInferArg(using m, mc) 56 | 57 | -------------------------------------------------------------------------------- /cats-effect/shared/src/main/scala/cps/monads/catsEffect/Using.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.catsEffect 2 | /* 3 | * (C) Ruslan Shevchenko 4 | * 2021 5 | */ 6 | 7 | import cats.* 8 | import cats.effect.* 9 | import cats.effect.kernel.* 10 | 11 | import cps.* 12 | 13 | 14 | /*** 15 | * Pseudo-synchronious syntax for resource, which can be used in async block. 16 | * 17 | * Usage: 18 | * assuming we have: 19 | * ``` 20 | * def open(file: File): Resource[IO, BufferedReader] 21 | * ``` 22 | * we can 23 | * ``` 24 | * async[IO] { 25 | * .... 26 | * open(file).useOn{ buffer => 27 | * await(doSomething) 28 | * buffer.write(r) 29 | * result 30 | * } 31 | * } 32 | * ``` 33 | **/ 34 | extension [F[_],A](r: Resource[F,A])(using m:CpsMonad[F], cm: MonadCancel[F,Throwable]) 35 | 36 | // bug or undefined specs in dotty: we can't name extension if we have method with the same name 37 | transparent inline def useOn[B](inline f: A=>B)(using CpsMonadContext[F]): B = 38 | await(r.use(a => m.pure(f(a)) )) 39 | 40 | 41 | /*** 42 | * Pseudo-synchronious syntax for resource, which can be used in async block. 43 | * 44 | * ``` 45 | * async[IO] { 46 | * .... 47 | * Resource.using(openFile){ buffer => 48 | * await(doSomething) 49 | * buffer.write(r) 50 | * result 51 | * } 52 | * } 53 | * ``` 54 | **/ 55 | extension (resourceSingleton: Resource.type) 56 | 57 | transparent inline def using[F[_],A, B](r:Resource[F,A])(inline f: A=>B)(using m:CpsMonad[F], cm: MonadCancel[F,Throwable], mc:CpsMonadContext[F]): B = 58 | await(r.use(a => m.pure(f(a)) )) 59 | 60 | 61 | transparent inline def using[F[_],A1, A2, B](r1:Resource[F,A1], r2:Resource[F,A2])(inline f: (A1,A2)=>B)(using m:CpsMonad[F], cm: MonadCancel[F,Throwable], mc: CpsMonadContext[F]): B = 62 | await(r1.use(a1 => r2.use(a2 => m.pure(f(a1,a2))))) 63 | 64 | 65 | transparent inline def using[F[_],A1, A2, A3, B](r1:Resource[F,A1], r2:Resource[F,A2], r3: Resource[F,A3])(inline f: (A1,A2,A3)=>B)(using m:CpsMonad[F], cm: MonadCancel[F,Throwable], mc: CpsMonadContext[F]): B = 66 | await(r1.use(a1 => r2.use(a2 => r3.use(a3 => m.pure(f(a1,a2,a3)))))) 67 | 68 | 69 | -------------------------------------------------------------------------------- /cats-effect/shared/src/main/scala/cps/monads/catsEffect/syntax/monadless/package.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.catsEffect.syntax.monadless 2 | 3 | import cats.effect.{MonadCancel, Resource} 4 | 5 | import cps.CpsTryMonad 6 | import cps.monads.catsEffect.AsyncScopeInferArg 7 | 8 | /** 9 | * Synonym for `asyncScope`. 10 | **/ 11 | def liftScope[F[_]](using m:CpsTryMonad[[A]=>>Resource[F,A]], mc:MonadCancel[F,Throwable]) = AsyncScopeInferArg(using m, mc) 12 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/ApiExampleDJSSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import scala.util.* 4 | import scala.concurrent.duration.* 5 | 6 | import cats.* 7 | import cats.effect.* 8 | 9 | 10 | import cps.* 11 | 12 | import cps.monads.catsEffect.{given,*} 13 | 14 | import munit.CatsEffectSuite 15 | 16 | 17 | object TestFuns { 18 | 19 | def myFun(using RunContext): IO[Boolean] = async[IO] { 20 | val results1 = await(talkToServer("request1", None)) 21 | await(IO.sleep(100.millis)) 22 | val results2 = await(talkToServer("request2", Some(results1.data))) 23 | if results2.isOk then 24 | await(writeToFile(results2.data)) 25 | await(IO.println("done")) 26 | true 27 | else 28 | await(IO.println("abort abort abort")) 29 | false 30 | } 31 | 32 | case class Result( 33 | isOk: Boolean, 34 | data: String 35 | ) 36 | 37 | case class RunContext(bingings: Map[String,Try[Result]]) 38 | 39 | def talkToServer(request:String, arg: Option[String])(using ctx: RunContext): IO[Result] = 40 | val key = s"talk-${request}-${arg.toString}" 41 | handleKey(key) 42 | 43 | def writeToFile(data:String)(using ctx:RunContext): IO[Unit] = 44 | val key = s"writeToFile-${data}" 45 | handleKey(key).map(_ => ()) 46 | 47 | def handleKey(key: String)(using ctx: RunContext): IO[Result] = 48 | ctx.bingings.get(key) match 49 | case Some(tryValue) => 50 | tryValue match 51 | case Success(r) => IO.delay(r) 52 | case Failure(ex) => IO.raiseError(ex) 53 | case None => 54 | IO.raiseError(RuntimeException(s"key $key is not found in run context")) 55 | 56 | } 57 | 58 | class ApiExampleDJSSuite extends CatsEffectSuite { 59 | 60 | import TestFuns.* 61 | 62 | test("make sure than API text running ok when all is ok") { 63 | given RunContext = RunContext(Map( 64 | "talk-request1-None" -> Success(Result(true,"answer1")), 65 | "talk-request2-Some(answer1)" -> Success(Result(true,"answer2")), 66 | "writeToFile-answer2" -> Success(Result(true,"ok")) 67 | )) 68 | TestFuns.myFun 69 | } 70 | 71 | test("make sure than we return error when 1-st text failed") { 72 | given RunContext = RunContext(Map( 73 | "talk-request1-None" -> Failure(RuntimeException("x1")), 74 | "talk-request2-Some(answer1)" -> Success(Result(true,"answer2")), 75 | "writeToFile-answer2" -> Success(Result(true,"ok")) 76 | )) 77 | interceptIO[RuntimeException]( 78 | TestFuns.myFun 79 | ) 80 | } 81 | 82 | test("make sure than we return false when 2-st request is not ok") { 83 | given RunContext = RunContext(Map( 84 | "talk-request1-None" -> Success(Result(true,"answer1")), 85 | "talk-request2-Some(answer1)" -> Success(Result(false,"answer2")), 86 | "writeToFile-answer2" -> Success(Result(true,"ok")) 87 | )) 88 | assertIO(TestFuns.myFun, false) 89 | 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/CatsEffectBackDoor.scala: -------------------------------------------------------------------------------- 1 | package cats.effect 2 | 3 | import cats.effect.unsafe.IORuntime 4 | 5 | import scala.concurrent.ExecutionContext 6 | 7 | object CatsEffectBackDoor { 8 | 9 | def blocking(ioRuntime: IORuntime): ExecutionContext = ioRuntime.blocking 10 | 11 | } 12 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/DCAIssue65Suite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import scala.concurrent.* 4 | import scala.concurrent.duration.* 5 | 6 | import cats._ 7 | import cats.effect._ 8 | import cps.{async, asyncStream, await} 9 | import cps.stream.AsyncList 10 | import cps.monads.given 11 | import cps.monads.catsEffect.{*,given} 12 | 13 | 14 | import munit.* 15 | 16 | 17 | class DCAIssue65Suite extends FunSuite { 18 | 19 | import cats.effect.unsafe.implicits.global 20 | 21 | val N = 100_000 22 | 23 | override val munitTimeout = Duration(300, "s") 24 | 25 | //given cps.macros.flags.PrintCode.type = cps.macros.flags.PrintCode 26 | 27 | def readingByIterator(ec: ExecutionContext, nIterations: Int)(implicit loc: munit.Location):Future[Long] = { 28 | println(s"ExecutionContext=$ec") 29 | given ExecutionContext = ec 30 | val stream: AsyncList[IO, Int] = asyncStream[AsyncList[IO, Int]] { out => 31 | out.emit(0) 32 | for i <- 1 to nIterations do out.emit(i) 33 | } 34 | val ite = stream.iterator 35 | val compute: IO[Long] = async[IO] { 36 | var n = await(ite.next) 37 | var res: Long = 0 38 | while (n.nonEmpty) { 39 | res += n.get 40 | n = await(ite.next) 41 | } 42 | res 43 | } 44 | println(s"readingByIterator: before unsafeToFuture") 45 | val retval = compute.unsafeToFuture() 46 | println(s"readingByIterator: after unsafeToFuture") 47 | retval 48 | } 49 | 50 | test("dotty-cps-async:65:global:reading by iterator with global execution context") { 51 | val nInteractions = System.getProperty("java.vm.name") match 52 | case "Scala Native" => 0 53 | case _ => N 54 | readingByIterator(ExecutionContext.global, nInteractions) 55 | } 56 | 57 | test("dotty-cps-async:65:global:reading by iterator with parasitic execution context") { 58 | readingByIterator(ExecutionContext.parasitic, N) 59 | } 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/FutureInteropSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | 4 | import cats.effect.{IO, SyncIO} 5 | import cats.effect.kernel.Async 6 | import munit.CatsEffectSuite 7 | 8 | import scala.concurrent._ 9 | import scala.concurrent.duration._ 10 | 11 | import cps._ 12 | import cps.monads.{*,given} 13 | import cps.monads.catsEffect.given 14 | 15 | import scala.concurrent.ExecutionContext.Implicits.global 16 | 17 | class FutureInteropSuite extends CatsEffectSuite { 18 | 19 | // use FutureAsyncAPI monad 20 | import cps.monads.{*,given} 21 | 22 | class FutureBasedAPI { 23 | 24 | def getX: Future[Int] = Future successful 3 25 | 26 | } 27 | 28 | class IOBasedAPI { 29 | 30 | def getY: IO[Int] = IO.delay(2) 31 | 32 | } 33 | 34 | class TaglessFinalAPI[F[_]:Async] { 35 | 36 | def getZ: F[Int] = summon[Async[F]].pure(1) 37 | 38 | } 39 | 40 | test("make sure that IO async can adopt Future") { 41 | val futureApi = new FutureBasedAPI() 42 | val ioApi = new IOBasedAPI() 43 | val run = async[IO] { 44 | val x = await(futureApi.getX) 45 | val y = await(ioApi.getY) 46 | assert(x + y == 5) 47 | } 48 | run 49 | } 50 | 51 | test("make sure that Future async can adopt IO") { 52 | val futureApi = new FutureBasedAPI() 53 | val ioApi = new IOBasedAPI() 54 | val run = async[Future] { 55 | val x = await(futureApi.getX) 56 | val y = await(ioApi.getY) 57 | assert(x + y == 5) 58 | } 59 | IO.fromFuture(IO.pure(run)) 60 | } 61 | 62 | test("make sure that F:Async async can adopt Future") { 63 | val futureApi = new FutureBasedAPI() 64 | def genericFun[F[_]:Async]():F[Int] = async[F] { 65 | val taglessApi = new TaglessFinalAPI[F]() 66 | val x = await(futureApi.getX) 67 | val z = await(taglessApi.getZ) 68 | val r = x + z 69 | assert(r == 4) 70 | r 71 | } 72 | 73 | val run = genericFun[IO]() 74 | run 75 | } 76 | 77 | test("make sure that F:Async is callable from IO") { 78 | val futureApi = new FutureBasedAPI() 79 | val ioApi = new IOBasedAPI() 80 | def genericFun[F[_]:Async]():F[Int] = async[F] { 81 | val taglessApi = new TaglessFinalAPI[F]() 82 | val x = await(futureApi.getX) 83 | val z = await(taglessApi.getZ) 84 | val r = x + z 85 | assert(r == 4) 86 | r 87 | } 88 | val run = async[IO] { 89 | val q = await(genericFun[IO]()) 90 | val y = await(ioApi.getY) 91 | assert(q == 4) 92 | assert(q + y == 6) 93 | } 94 | run 95 | } 96 | 97 | 98 | test("make sure tagless API wrapped in IO is callable from Future") { 99 | val futureApi = new FutureBasedAPI() 100 | val run = async[Future] { 101 | val taglessApi = new TaglessFinalAPI[IO]() 102 | val x = await(futureApi.getX) 103 | val z = await(taglessApi.getZ) 104 | val r = x + z 105 | assert(r == 4) 106 | } 107 | run 108 | } 109 | 110 | 111 | 112 | } 113 | 114 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/LazyEffectSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import scala.language.implicitConversions 4 | import scala.util.* 5 | import scala.concurrent.duration.* 6 | 7 | import cats.* 8 | import cats.effect.* 9 | 10 | 11 | import cps.* 12 | 13 | import cps.monads.catsEffect.{given,*} 14 | 15 | import munit.CatsEffectSuite 16 | 17 | 18 | class LazyEffectSuite extends CatsEffectSuite { 19 | 20 | test("make sure that async expressions are not evaluating early") { 21 | //implicit val printCode = cps.macroFlags.PrintCode 22 | var x = 0 23 | val c = async { 24 | x = 1 25 | } 26 | assert(x == 0) 27 | c 28 | } 29 | 30 | test("make sure that exception is catched inside async expression ") { 31 | //implicit val printCode = cps.macroFlags.PrintCode 32 | val c1 = async { 33 | throw new RuntimeException("AAA") 34 | } 35 | async { 36 | var x = 0 37 | var y = 0 38 | try { 39 | await(c1) 40 | x = 1 41 | }catch{ 42 | case ex: RuntimeException => 43 | assert(ex.getMessage()=="AAA") 44 | y = 2 45 | } 46 | assert(x == 0) 47 | assert(y == 2) 48 | } 49 | } 50 | 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/ResourceMonadSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import scala.concurrent.duration._ 4 | import scala.collection.mutable.ArrayBuffer 5 | 6 | import cats.effect.* 7 | import cats.effect.kernel.* 8 | 9 | import cps.* 10 | import cps.monads.catsEffect.{given,*} 11 | 12 | import munit.CatsEffectSuite 13 | 14 | import java.nio.ByteBuffer 15 | import java.nio.channels.FileChannel 16 | import java.nio.file.Path 17 | import java.nio.file.Paths 18 | import java.nio.file.{OpenOption, StandardOpenOption} 19 | 20 | class DataEmu(val data: String): 21 | var closed = false 22 | 23 | def close(): Unit = 24 | closed = true 25 | 26 | 27 | class FileMinimalEmu(val name: String, val ref: Ref[IO,Vector[Array[Byte]]]): 28 | var closed = false 29 | 30 | def write(data: Array[Byte]): IO[Unit] = 31 | if (closed) 32 | throw new RuntimeException("File is closed") 33 | ref.update(parts => parts :+ data) 34 | 35 | def readAll(): IO[Array[Byte]] = 36 | ref.get.map(v => _readAll(v)) 37 | 38 | def close(): Unit = 39 | closed = true 40 | 41 | def _readAll(v: Vector[Array[Byte]] ): Array[Byte] = 42 | val len = v.map(_.length).sum 43 | val retval = new Array[Byte](len) 44 | val _ = v.foldLeft(0){ (s,e) => 45 | System.arraycopy(e,0,retval,s,e.length) 46 | s + e.length 47 | } 48 | retval 49 | 50 | 51 | 52 | class ResourceMonadSuite extends CatsEffectSuite { 53 | 54 | def createDataEmu(data: String):Resource[IO,DataEmu] = 55 | Resource.make(acquire=IO.delay(new DataEmu(data)))(release = data => IO(data.close())) 56 | 57 | def createFileEmu(name: String):Resource[IO,FileMinimalEmu] = 58 | Resource.make( 59 | acquire=Ref.of[IO,Vector[Array[Byte]]](Vector.empty).map(r => new FileMinimalEmu(name,r)) 60 | )(release = data => IO(data.close())) 61 | 62 | test("use cats resource as scope") { 63 | val prg = asyncScope[IO] { 64 | val input = await(createDataEmu("AAA AC")) 65 | val output = await(createFileEmu("output")) 66 | await(output.write(input.data.getBytes())) 67 | await(output.write("\nBBB BC".getBytes())) 68 | val data = await(output.readAll()) 69 | (input.closed, output.closed, input, output, data) 70 | } 71 | prg.map{ v => 72 | val (inputClosedInside, outputClosedInside, input, output, data) = v 73 | assert(!inputClosedInside) 74 | assert(!outputClosedInside) 75 | assert(input.closed) 76 | assert(output.closed) 77 | assert(new String(data).startsWith("AAA AC")) 78 | } 79 | } 80 | 81 | 82 | test("use cats resource as scope [removed automaticColoring]") { 83 | val prg = asyncScope[IO] { 84 | val input = await(createDataEmu("AAA AC")) 85 | val output = await(createFileEmu("output")) 86 | await(output.write(input.data.getBytes())) 87 | await(output.write("\nBBB BC".getBytes())) 88 | val data = output.readAll() 89 | (input.closed, output.closed, input, output, await(data)) 90 | } 91 | prg.map{ v => 92 | val (inputClosedInside, outputClosedInside, input, output, data) = v 93 | println(s"data=$data, new String(data)=${new String(data)}") 94 | assert(!inputClosedInside) 95 | assert(!outputClosedInside) 96 | assert(input.closed) 97 | assert(output.closed) 98 | assert(new String(data).startsWith("AAA AC")) 99 | } 100 | 101 | } 102 | 103 | 104 | } -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/ResourceSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffecct 2 | 3 | import scala.concurrent.duration._ 4 | import scala.collection.mutable.ArrayBuffer 5 | 6 | import cats.effect.* 7 | import cats.effect.kernel.* 8 | 9 | import cps.* 10 | import cps.monads.catsEffect.{given,*} 11 | 12 | import munit.CatsEffectSuite 13 | 14 | 15 | class WriterEmu { 16 | 17 | var isClosed: Boolean = false 18 | val messages: ArrayBuffer[String] = ArrayBuffer.empty 19 | 20 | def write(message:String): Unit = { 21 | if (!isClosed) 22 | messages.append(message) 23 | else 24 | throw new IllegalStateException("resource is already closed") 25 | } 26 | 27 | def close(): Unit = 28 | isClosed = true 29 | 30 | } 31 | 32 | 33 | 34 | 35 | class ResourceSuite extends CatsEffectSuite { 36 | 37 | def makeWriterEmu(): Resource[IO,WriterEmu] = 38 | Resource.make(IO.delay(new WriterEmu()))(a => IO.delay(a.close())) 39 | 40 | test("use cats resource") { 41 | val prg = async[IO] { 42 | val r = makeWriterEmu() 43 | val ctr = await(IO.ref(0)) 44 | Resource.using(r) { a => 45 | val c0 = await(ctr.get) 46 | a.write(s"AAA-${c0}") 47 | await(ctr.update(_ + 1)) 48 | val c1 = await(ctr.get) 49 | a.write(s"BBB-${c1}") 50 | (a, a.messages.toList, a.isClosed) 51 | } 52 | } 53 | prg.map{ 54 | (a, messages, isClosedInside) => 55 | //println(s"a = $a") 56 | //println(s"messages = $messages") 57 | assert(a.isClosed) 58 | assert(!isClosedInside) 59 | } 60 | 61 | } 62 | 63 | } 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/StupidFizzBuzzSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import scala.language.implicitConversions 4 | import cats.effect.{IO, SyncIO} 5 | import munit.CatsEffectSuite 6 | 7 | import concurrent.duration.* 8 | import cps.* 9 | import cps.monads.catsEffect.given 10 | 11 | import scala.annotation.experimental 12 | 13 | @experimental 14 | class StupidFizzBuzzSuite extends CatsEffectSuite { 15 | import cps.catsEffect.directRefs.{*, given} 16 | 17 | 18 | test("make sure that FizBuzz run N times") { 19 | val run = 20 | for { 21 | logger <- ToyLogger.make() 22 | ctr <- IO.ref(0) 23 | 24 | wait = IO.sleep(100.millisecond) 25 | poll = wait *> ctr.get 26 | 27 | _ <- (poll.flatMap(x => 28 | for{ _ <- logger.log(x.toString) 29 | _ <- if(x % 3 == 0) { logger.log("fizz") } else IO.unit 30 | _ <- if(x % 5 == 0) { logger.log("buzz") } else IO.unit 31 | } yield () 32 | ) *> ctr.update(_ + 1) *> ctr.get ).iterateWhile(_ <= 10) 33 | logs <- logger.all() 34 | } yield logs 35 | run.flatMap{ logs => 36 | assert(logs(0)=="0") 37 | assert(logs(1)=="fizz") 38 | assert(logs(2)=="buzz") 39 | assert(logs(3)=="1") 40 | assert(logs(4)=="2") 41 | assert(logs(5)=="3") 42 | assert(logs(6)=="fizz") 43 | assert(logs(7)=="4") 44 | //IO.println(logs) 45 | IO.unit 46 | } 47 | } 48 | 49 | test("make sure that FizBuzz run N times in async loop") { 50 | val run = async { 51 | val logger = await(ToyLogger.make()) 52 | val ctr = await(IO.ref(0)) 53 | while { 54 | //await(IO.sleep(100.millisecond)) 55 | val v = await(ctr.get) 56 | await(logger.log(v.toString)) 57 | if v % 3 == 0 then 58 | await(logger.log("fizz")) 59 | if v % 5 == 0 then 60 | await(logger.log("buzz")) 61 | await(ctr.update(_ + 1)) 62 | v < 10 63 | } do () 64 | await(logger.all()) 65 | } 66 | run.flatMap{logs => 67 | assert(logs(0)=="0") 68 | assert(logs(6)=="fizz") 69 | //IO.println(logs) 70 | IO.unit 71 | } 72 | } 73 | 74 | test("make sure that FizBuzz run N times in async loop [removed: with automatic coloring]") { 75 | val run = async { 76 | val logger = await(ToyLogger.make()) 77 | val ctr = await(IO.ref(0)) 78 | while { 79 | //await(IO.sleep(100.millisecond)) 80 | val v = await(ctr.get) 81 | await(logger.log(v.toString)) 82 | if v % 3 == 0 then 83 | await(logger.log("fizz")) 84 | if v % 5 == 0 then 85 | await(logger.log("buzz")) 86 | await(ctr.update(_ + 1)) 87 | v < 10 88 | } do () 89 | await(logger.all()) 90 | } 91 | run.flatMap{logs => 92 | assert(logs(0)=="0") 93 | assert(logs(6)=="fizz") 94 | //IO.println(logs) 95 | IO.unit 96 | } 97 | } 98 | 99 | test("make sure that FizBuzz run N times with direct context") { 100 | //implicit val printCode = cps.macros.flags.PrintCode 101 | val run = async { 102 | val logger = DToyLogger.make() 103 | val ctr = IO.directRefOf(0) 104 | while { 105 | //await(IO.sleep(100.millisecond)) 106 | val v = ctr.get 107 | logger.log(v.toString) 108 | if v % 3 == 0 then 109 | logger.log("fizz") 110 | if v % 5 == 0 then 111 | logger.log("buzz") 112 | ctr.update(_ + 1) 113 | v < 10 114 | } do () 115 | logger.all() 116 | } 117 | run.flatMap{ logs => 118 | assert(logs(0)=="0") 119 | assert(logs(6)=="fizz") 120 | IO.unit 121 | } 122 | } 123 | 124 | } 125 | 126 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/ToyLogger.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import cats.* 4 | import cats.effect.* 5 | import cps.* 6 | 7 | import scala.annotation.experimental 8 | 9 | class ToyLogger(ref: Ref[IO,Vector[String]]): 10 | 11 | def log(message: String):IO[Unit] = 12 | ref.update(lines => lines :+ message) 13 | 14 | def all(): IO[Vector[String]] = 15 | ref.get 16 | 17 | 18 | object ToyLogger: 19 | 20 | def make(): IO[ToyLogger] = 21 | Ref.of[IO,Vector[String]](Vector.empty).map(ToyLogger(_)) 22 | 23 | 24 | @experimental 25 | class DToyLogger(ref: Ref[IO,Vector[String]]): 26 | 27 | def log(message: String)(using CpsDirect[IO]): Unit = 28 | await(ref.update(lines => lines :+ message)) 29 | 30 | 31 | def all()(using CpsDirect[IO]): Vector[String] = 32 | await(ref.get) 33 | 34 | @experimental 35 | object DToyLogger: 36 | 37 | def make()(using CpsDirect[IO]): DToyLogger = 38 | val ref = await(Ref.of[IO,Vector[String]](Vector.empty)) 39 | DToyLogger(ref) 40 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/cps/catsEffect/TryFinallyCancellableSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.catsEffect 2 | 3 | import cats.effect.{IO, SyncIO} 4 | import cats.effect.kernel.MonadCancel 5 | import cats.effect.unsafe.IORuntime 6 | import munit.CatsEffectSuite 7 | 8 | import concurrent.duration.* 9 | import cps.* 10 | import cps.monads.catsEffect.given 11 | 12 | import scala.concurrent.{CancellationException, ExecutionContext} 13 | import scala.util.{Failure, Success} 14 | import scala.util.control.NonFatal 15 | 16 | 17 | class TryFinallyCancellableSuite extends CatsEffectSuite { 18 | 19 | test("L00: 0000 ensure that finally sync block is executed") { 20 | var x = 0 21 | var finalizerCalled = false 22 | val run = async[IO] { 23 | try { 24 | x = 1 25 | x = x + 1 26 | x = x + 1 27 | x 28 | } finally { 29 | x = x + 1 30 | finalizerCalled = true 31 | } 32 | } 33 | run.map { _ => 34 | assert(finalizerCalled) 35 | assert(x == 4) 36 | } 37 | } 38 | 39 | //given cps.macros.flags.PrintCode.type = cps.macros.flags.PrintCode 40 | 41 | 42 | test("L001: 1200 ensure that finally blok is executed after cancelled") { 43 | 44 | var finalizerCalled = false 45 | val run = async[IO] { 46 | try { 47 | IO.canceled.await 48 | } finally { 49 | finalizerCalled = true 50 | } 51 | } 52 | 53 | // ironically, CatsEffectSuite does not have method to check cancellation. 54 | // 55 | //interceptIO[CancellationException](run).map { _ => 56 | // assert(finalizerCalled) 57 | //} 58 | // not working, because Cancellation omit exception handlers. 59 | given IORuntime = munitIORuntime 60 | 61 | val outcomeFuture = run.unsafeToFuture().transform { 62 | case Success(_) => 63 | assert(finalizerCalled) 64 | Success(Success(())) 65 | case Failure(ex) => 66 | assert(finalizerCalled) 67 | Success(Failure(ex)) 68 | } 69 | IO.fromFuture(IO.pure(outcomeFuture)).map { result => 70 | result match 71 | case Failure(ex: CancellationException) => 72 | //println(s"L001, CancellationException") 73 | case Failure(ex) => 74 | assert(false, s"should have CancellationException we have $ex") 75 | assert(finalizerCalled) 76 | } 77 | 78 | 79 | } 80 | 81 | 82 | test("L002: 1210 ensure that async finally blok is executed after cancelled with async finalizer") { 83 | 84 | var finalizerCalled = false 85 | var x = 0 86 | var y = 0 87 | val run = async[IO] { 88 | try { 89 | x = await(IO.delay(1)) 90 | IO.canceled.await 91 | } finally { 92 | //println("in async finalizer") 93 | y = await(IO.delay(2)) 94 | finalizerCalled = true 95 | } 96 | } 97 | 98 | 99 | given IORuntime = munitIORuntime 100 | 101 | val outcomeFuture = run.unsafeToFuture().transform { 102 | case Success(_) => 103 | Failure(new RuntimeException("computation should be cancelled")) 104 | case Failure(ex) => 105 | assert(ex.isInstanceOf[CancellationException], s"unexpected exception: $ex") 106 | Success(()) 107 | } 108 | IO.fromFuture(IO.pure(outcomeFuture)).map { _ => 109 | assert(finalizerCalled) 110 | assert(x == 1) 111 | assert(y == 2) 112 | } 113 | } 114 | 115 | 116 | test("L003: 1211 ensure that async finally blok is executed after cancelled, and exception from it is exists somewhere") { 117 | 118 | var finalizerCalled = false 119 | var x = 0 120 | val run = async[IO] { 121 | try { 122 | IO.canceled.await 123 | } finally { 124 | x = await(IO.delay(1)) 125 | finalizerCalled = true 126 | throw new RuntimeException("AAA") 127 | } 128 | } 129 | 130 | var runtimeExceptionCatched = false 131 | //try 132 | val defaultCompute = munitIORuntime.compute 133 | var exceptionCatcher: (Throwable => Boolean) = (ex) => false 134 | val myDelegateExecutionContext = new ExecutionContext { 135 | override def execute(runnable: Runnable): Unit = { 136 | defaultCompute.execute( 137 | new Runnable { 138 | def run(): Unit = 139 | try 140 | runnable.run() 141 | catch 142 | case NonFatal(ex) => 143 | reportFailure(ex) 144 | // let default also reports it 145 | throw ex 146 | } 147 | ) 148 | } 149 | 150 | override def reportFailure(cause: Throwable): Unit = { 151 | if (exceptionCatcher(cause)) { 152 | runtimeExceptionCatched = true 153 | } 154 | //println(s"myDelegateExecutionContext: exception: $cause") 155 | } 156 | } 157 | val outcome = { 158 | given IORuntime = IORuntime( 159 | compute = myDelegateExecutionContext, 160 | blocking = _root_.cats.effect.CatsEffectBackDoor.blocking(munitIORuntime), 161 | scheduler = munitIORuntime.scheduler, 162 | shutdown = munitIORuntime.shutdown, 163 | config = munitIORuntime.config 164 | ) 165 | 166 | exceptionCatcher = (ex) => ex.getMessage() == "AAA" 167 | run.unsafeToFuture().transform { 168 | case Success(_) => 169 | Failure(new RuntimeException("computation should be cancelled")) 170 | case Failure(ex) => 171 | assert(ex.isInstanceOf[CancellationException], s"unexpected exception: $ex") 172 | Success(ex) 173 | } 174 | } 175 | println(s"outcome: $outcome") 176 | //catch 177 | // case ex: CancellationException => 178 | // // 179 | // println(s"ex: $ex") 180 | // case ex: RuntimeException => 181 | // runtimeExceptionCatched = true 182 | // println(s"ex: $ex") 183 | IO.fromFuture(IO.pure(outcome)).map { _ => 184 | assert(x == 1) 185 | assert(finalizerCalled) 186 | assert(runtimeExceptionCatched) 187 | } 188 | 189 | } 190 | 191 | test("L004: 1200 check that finally executed after the main block with cancellation") { 192 | var x = 0 193 | val run = async[IO] { 194 | try { 195 | x = 2 196 | IO.canceled.await 197 | } finally { 198 | if (x == 2) then 199 | x = 3 200 | else 201 | x = 1 202 | } 203 | } 204 | 205 | given IORuntime = munitIORuntime 206 | 207 | val runFuture = run.unsafeToFuture().transform { 208 | case Success(_) => 209 | Failure(new RuntimeException("computation should be cancelled")) 210 | case Failure(ex) => 211 | assert(ex.isInstanceOf[CancellationException], s"unexpected exception: $ex") 212 | assert(x == 3) 213 | Success(ex) 214 | } 215 | IO.fromFuture(IO.pure(runFuture)) 216 | 217 | } 218 | 219 | test("L005: 1000 check that finally executed after the async main block without exception") { 220 | 221 | //given cps.macros.flags.PrintCode.type = cps.macros.flags.PrintCode 222 | 223 | var x = 0 224 | val run = async[IO] { 225 | try { 226 | await(IO.delay(1)) 227 | x = 2 228 | } finally { 229 | if (x == 2) then 230 | x = 3 231 | else 232 | x = 1 233 | } 234 | } 235 | 236 | run.map(_ => assert(x == 3)) 237 | 238 | } 239 | 240 | test("L006: 0000 check that finally executed after the sync main block without exception") { 241 | 242 | //given cps.macros.flags.PrintCode.type = cps.macros.flags.PrintCode 243 | 244 | var x = 0 245 | var mainBlockCalls = 0 246 | var finalizerCalls = 0 247 | val run = async[IO] { 248 | try { 249 | mainBlockCalls = mainBlockCalls + 1 250 | x = 2 251 | } finally { 252 | finalizerCalls = finalizerCalls + 1 253 | if (x == 2) then 254 | x = 3 255 | else 256 | x = 1 257 | } 258 | } 259 | 260 | run.map { _ => 261 | assert(x == 3) 262 | assert(mainBlockCalls == 1) 263 | assert(finalizerCalls == 1) 264 | } 265 | 266 | } 267 | 268 | 269 | test("L007: 1010 check that finally executed after the ascync main block without exception with async finalizer") { 270 | 271 | given cps.macros.flags.PrintCode.type = cps.macros.flags.PrintCode 272 | 273 | @volatile var x = 0 274 | @volatile var y = 0 275 | @volatile var nMainCalls = 0 276 | @volatile var nFinalizerCalls = 0 277 | val run = async[IO] { 278 | try { 279 | x = 2 280 | await(IO.delay(1)) 281 | nMainCalls = nMainCalls + 1 282 | } finally { 283 | nFinalizerCalls = nFinalizerCalls + 1 284 | y = await(IO.delay(2)) 285 | if (x == 2) then 286 | x = 3 287 | else 288 | println(s"L007: unexpected x: $x") 289 | x = 1 290 | } 291 | } 292 | 293 | 294 | val r1 = run.map { _ => 295 | if (nFinalizerCalls != 1) { 296 | println(s"L007: nFinalizerCalls = $nFinalizerCalls") 297 | } 298 | assert(nFinalizerCalls == 1) 299 | assert(nMainCalls == 1) 300 | assert(x == 3) 301 | assert(y == 2) 302 | } 303 | 304 | r1 305 | 306 | 307 | } 308 | 309 | test("L008: 1100 check that finally executed after the async main block with exception") { 310 | @volatile var x = 0 311 | @volatile var mainBlockCalled = 0 312 | @volatile var finalizerCalled = 0 313 | val run = async[IO] { 314 | try { 315 | x = 2 316 | mainBlockCalled = mainBlockCalled + 1 317 | await(IO.raiseError(new RuntimeException("AAA"))) 318 | } finally { 319 | finalizerCalled = finalizerCalled + 1 320 | if (x == 2) then 321 | x = 3 322 | else 323 | x = 1 324 | } 325 | } 326 | 327 | run.intercept[RuntimeException].map { _ => 328 | if (x != 3) { 329 | println(s"x = $x, mainBlockCalled = $mainBlockCalled, finalizerCalled = $finalizerCalled") 330 | } 331 | assert(x == 3) 332 | assert(mainBlockCalled == 1) 333 | assert(finalizerCalled == 1) 334 | } 335 | 336 | } 337 | 338 | test("L009: check that finalizer on exception called twice without syntax sugar") { 339 | @volatile var finalizerCalls = 0 340 | 341 | summon[MonadCancel[IO, Throwable]].guaranteeCase { 342 | IO.raiseError(new RuntimeException("AAA")) 343 | } { 344 | case _ => 345 | IO.pure(()).map { _ => 346 | finalizerCalls = finalizerCalls + 1 347 | } 348 | }.intercept[RuntimeException].map { _ => 349 | assert(finalizerCalls == 1) 350 | } 351 | 352 | 353 | } 354 | 355 | test("L008: 1110 check that async finally executed after the async main block with exception") { 356 | 357 | @volatile var x = 0 358 | @volatile var y = 0 359 | @volatile var mainBlockCalled = 0 360 | @volatile var finalizerCalled = 0 361 | val run = async[IO] { 362 | try { 363 | x = await(IO.delay(2)) 364 | mainBlockCalled = mainBlockCalled + 1 365 | throw RuntimeException("AAA") 366 | } finally { 367 | y = await(IO.delay(1)) 368 | finalizerCalled = finalizerCalled + 1 369 | if (x == 2) then 370 | x = 3 371 | else 372 | x = 1 373 | } 374 | } 375 | 376 | run.intercept[RuntimeException].map { _ => 377 | assert(x == 3) 378 | assert(y == 1) 379 | assert(mainBlockCalled == 1) 380 | assert(finalizerCalled == 1) 381 | } 382 | 383 | } 384 | 385 | test("L009 check that during normal execution errors from the finalizer are propagated (async boddy") { 386 | @volatile var finalizerCalls = 0 387 | val run = async[IO] { 388 | try { 389 | await(IO.pure(())) 390 | } finally { 391 | finalizerCalls = finalizerCalls + 1 392 | throw new RuntimeException("AAA") 393 | } 394 | } 395 | run.redeem( 396 | ex => assert(ex.getMessage() == "AAA"), 397 | _ => assert(false, "exception should be thrown from finalizer") 398 | ).map { _ => 399 | assert(finalizerCalls == 1) 400 | } 401 | } 402 | 403 | test("L010 check that during error handling errors from the finalizer are propagated (async body)") { 404 | @volatile var finalizerCalls = 0 405 | val run = async[IO] { 406 | try { 407 | await(IO.raiseError(new RuntimeException("BBB"))) 408 | } finally { 409 | finalizerCalls = finalizerCalls + 1 410 | throw new RuntimeException("AAA") 411 | } 412 | } 413 | run.redeem( 414 | ex => { 415 | println(s"ex=${ex}") 416 | //ex.printStackTrace() 417 | assert(ex.getMessage() == "AAA") 418 | assert(ex.getSuppressed().length == 1) 419 | assert(ex.getSuppressed()(0).getMessage() == "BBB") 420 | } , 421 | _ => assert(false, "exception should be thrown from finalizer") 422 | ).map { _ => 423 | assert(finalizerCalls == 1) 424 | } 425 | 426 | } 427 | 428 | test("L009 check that during normal execution errors from the finalizer are propagated (sync boddy)") { 429 | @volatile var finalizerCalls = 0 430 | val run = async[IO] { 431 | try { 432 | 1 433 | } finally { 434 | finalizerCalls = finalizerCalls + 1 435 | throw new RuntimeException("AAA") 436 | } 437 | } 438 | run.redeem( 439 | ex => assert(ex.getMessage() == "AAA"), 440 | _ => assert(false, "exception should be thrown from finalizer") 441 | ).map { _ => 442 | assert(finalizerCalls == 1) 443 | } 444 | } 445 | 446 | test("L010 check that during error handling errors from the finalizer are propagated (sync body)") { 447 | @volatile var finalizerCalls = 0 448 | val run = async[IO] { 449 | try { 450 | throw new RuntimeException("BBB") 451 | } finally { 452 | finalizerCalls = finalizerCalls + 1 453 | throw new RuntimeException("AAA") 454 | } 455 | } 456 | run.redeem( 457 | ex => { 458 | println(s"ex=${ex}") 459 | //ex.printStackTrace() 460 | assert(ex.getMessage() == "AAA") 461 | assert(ex.getSuppressed().length == 1) 462 | assert(ex.getSuppressed()(0).getMessage() == "BBB") 463 | } , 464 | _ => assert(false, "exception should be thrown from finalizer") 465 | ).map { _ => 466 | assert(finalizerCalls == 1) 467 | } 468 | } 469 | 470 | } 471 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/midgard/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | package midgard 2 | 3 | import cats.effect.IO 4 | import cps.* 5 | import cps.monads.catsEffect.{*,given} 6 | 7 | 8 | import scala.annotation.experimental 9 | 10 | @experimental 11 | object HelloWorld { 12 | 13 | def say(): IO[String] = IO.delay("Hello, cats!") 14 | 15 | def sayDirect(using CpsDirect[IO]): String = "Hello, cats[direct]" 16 | 17 | } 18 | -------------------------------------------------------------------------------- /cats-effect/shared/src/test/scala/midgard/HelloWorldSuite.scala: -------------------------------------------------------------------------------- 1 | package midgard 2 | 3 | import scala.annotation.experimental 4 | import cats.effect.* 5 | import cps.* 6 | import cps.monads.catsEffect.{*,given} 7 | 8 | 9 | import munit.CatsEffectSuite 10 | 11 | @experimental 12 | class HelloWorldSuite extends CatsEffectSuite { 13 | 14 | test("dotty-cps-async") { 15 | val run =async[IO] { 16 | HelloWorld.sayDirect 17 | } 18 | run 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /monix/js/src/test/scala/cps/monixtest/SingleThreadScheduler.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import monix.execution.* 4 | import monix.execution.schedulers.* 5 | 6 | 7 | object SingleThreadScheduler: 8 | 9 | def apply(): Scheduler = Scheduler.global 10 | 11 | -------------------------------------------------------------------------------- /monix/jvm/src/test/scala/cps/monixtest/SingleThreadScheduler.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import monix.execution.* 4 | import monix.execution.schedulers.* 5 | 6 | 7 | object SingleThreadScheduler: 8 | 9 | def apply(): SchedulerService = Scheduler.singleThread(name="monix-toy-logger") 10 | 11 | -------------------------------------------------------------------------------- /monix/shared/src/main/scala/cps/monads/monix/MonixCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.monix 2 | /* 3 | * (C) Ruslan Shevchenko 4 | * 2021 5 | */ 6 | 7 | 8 | import monix.eval.* 9 | import monix.execution.* 10 | import cps.* 11 | 12 | import scala.util.* 13 | import scala.concurrent.* 14 | 15 | /** 16 | * CpsMonad for Monix Task 17 | **/ 18 | given MonixCpsMonad: CpsConcurrentEffectMonad[Task] with CpsMonadInstanceContext[Task] with 19 | 20 | override type Spawned[A] = Fiber[A] 21 | 22 | def pure[T](t:T): Task[T] = Task.pure(t) 23 | 24 | def map[A,B](fa:Task[A])(f: A=>B): Task[B] = 25 | fa.map(f) 26 | 27 | def flatMap[A,B](fa:Task[A])(f: A=>Task[B]): Task[B] = 28 | fa.flatMap(f) 29 | 30 | def error[A](e: Throwable): Task[A] = 31 | Task.raiseError[A](e) 32 | 33 | def flatMapTry[A,B](fa: Task[A])(f: Try[A]=>Task[B]): Task[B] = 34 | fa.materialize.flatMap(f) 35 | 36 | def adoptCallbackStyle[A](source: (Try[A]=>Unit)=>Unit): Task[A] = 37 | Task.async{ 38 | callback => source(r => callback.apply(r)) 39 | } 40 | 41 | def spawnEffect[A](op: => Task[A]) = 42 | op.start 43 | 44 | def join[A](fiber: Fiber[A]) = fiber.join 45 | 46 | def tryCancel[A](op: Fiber[A]): Task[Unit] = 47 | op.cancel 48 | 49 | 50 | 51 | 52 | given futureToTask: CpsMonadConversion[Future,Task] with 53 | def apply[T](ft: Future[T]): Task[T] = Task.fromFuture(ft) 54 | 55 | given taskToFuture(using Scheduler): CpsMonadConversion[Task,Future] with 56 | def apply[T](ft: Task[T]):Future[T] = ft.runToFuture 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /monix/shared/src/main/scala/cps/stream/monix/ObservableEmitAbsorber.scala: -------------------------------------------------------------------------------- 1 | package cps.stream.monix 2 | 3 | import cps.monads.monix.{*,given} 4 | 5 | import monix.eval.* 6 | import monix.execution.* 7 | import monix.reactive.* 8 | 9 | import cps.* 10 | import cps.stream.* 11 | import scala.concurrent.* 12 | 13 | given ObservableEmitAbsorber[T](using ExecutionContext): BaseUnfoldCpsAsyncEmitAbsorber[Observable[T],Task, CpsMonadInstanceContextBody[Task], T] with 14 | 15 | override type Element = T 16 | 17 | def asSync(task: Task[Observable[T]]): Observable[T] = 18 | Observable.fromTask(task).flatten 19 | 20 | 21 | def unfold[S](s0:S)(f:S => Task[Option[(T,S)]]):Observable[T] = 22 | Observable.unfoldEval[S,T](s0)(f) 23 | -------------------------------------------------------------------------------- /monix/shared/src/test/scala/cps/monixtest/BasicGeneratorSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import scala.concurrent.* 4 | 5 | import cps.* 6 | import monix.* 7 | import monix.reactive.* 8 | import cps.monads.monix.given 9 | import cps.stream.monix.given 10 | 11 | 12 | import munit.* 13 | 14 | import monix.execution.Scheduler.Implicits.global 15 | 16 | class BasicGeneratorSuite extends FunSuite { 17 | 18 | val N = 10000 19 | 20 | test("simple loop in monix") { 21 | 22 | val stream = asyncStream[Observable[Int]] { out => 23 | for(i <- 1 to N) { 24 | out.emit(i) 25 | } 26 | } 27 | 28 | stream.consumeWith(Consumer.toList).runToFuture.map{ v => 29 | assert(v.length == N) 30 | assert(v(0)==1) 31 | assert(v(1)==2) 32 | } 33 | 34 | } 35 | 36 | test("exception should break loop: monix") { 37 | val stream = asyncStream[Observable[Int]] { out => 38 | for(i <- 1 to N) { 39 | if (i == N/2) then 40 | throw new RuntimeException("bye") 41 | out.emit(i) 42 | } 43 | } 44 | 45 | val res = stream.consumeWith(Consumer.toList).runToFuture 46 | 47 | res.failed.map(ex => assert(ex.getMessage()=="bye")) 48 | 49 | } 50 | 51 | 52 | } -------------------------------------------------------------------------------- /monix/shared/src/test/scala/cps/monixtest/ConcurrentSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import cps.* 4 | import cps.monads.monix.{given,*} 5 | import monix.eval.* 6 | import java.util.concurrent.atomic.AtomicInteger 7 | 8 | import munit.* 9 | 10 | class ConcurrentSuite extends FunSuite { 11 | 12 | test("basic concurrent test") { 13 | 14 | val run = async[Task] { 15 | val m = summon[CpsConcurrentEffectMonad[Task]] 16 | val x = new AtomicInteger(0) 17 | val fiber1 = await(m.spawnEffect{ 18 | Task.now{ x.incrementAndGet() } 19 | }) 20 | val fiber2 = await(m.spawnEffect{ 21 | Task.now{ x.incrementAndGet() } 22 | }) 23 | val y1 = await(m.join(fiber1)) 24 | val y2 = await(m.join(fiber2)) 25 | assert(x.get() == 2 ) 26 | } 27 | 28 | } 29 | 30 | // TODO: add test for cancel. 31 | 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /monix/shared/src/test/scala/cps/monixtest/FutureInteropSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import scala.concurrent._ 4 | import scala.concurrent.duration._ 5 | import scala.concurrent.ExecutionContext.Implicits.global 6 | 7 | 8 | import monix.eval.* 9 | 10 | import cps._ 11 | import cps.monads.given 12 | import cps.monads.monix.given 13 | import munit._ 14 | 15 | 16 | 17 | 18 | class FutureInteropSuite extends FunSuite { 19 | 20 | class FutureBasedAPI { 21 | 22 | def getX: Future[Int] = Future successful 3 23 | 24 | } 25 | 26 | class TaskBasedAPI { 27 | 28 | def getY: Task[Int] = Task.pure(2) 29 | 30 | } 31 | 32 | 33 | test("make sure that Task async can adopt Future") { 34 | val futureApi = new FutureBasedAPI() 35 | val taskApi = new TaskBasedAPI() 36 | val run = async[Task] { 37 | val x = await(futureApi.getX) 38 | val y = await(taskApi.getY) 39 | assert(x + y == 5) 40 | } 41 | import monix.execution.Scheduler.Implicits.global 42 | run.runToFuture 43 | } 44 | 45 | test("make sure that Future async can adopt Task") { 46 | import monix.execution.Scheduler.Implicits.global 47 | 48 | val futureApi = new FutureBasedAPI() 49 | val taskApi = new TaskBasedAPI() 50 | val run = async[Future] { 51 | val x = await(futureApi.getX) 52 | val y = await(taskApi.getY) 53 | assert(x + y == 5) 54 | } 55 | run 56 | } 57 | 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /monix/shared/src/test/scala/cps/monixtest/LazyEffectSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import scala.language.implicitConversions 4 | import scala.concurrent.duration.* 5 | 6 | import monix.execution.atomic.* 7 | import monix.eval.* 8 | 9 | import cps.* 10 | import cps.monads.monix.given 11 | 12 | import munit.* 13 | 14 | class LazyEffectSuite extends FunSuite { 15 | 16 | import monix.execution.Scheduler.Implicits.global 17 | 18 | test("monix: make sure that async expressions are not evaluating early") { 19 | //implicit val printCode = cps.macroFlags.PrintCode 20 | var x = 0 21 | val c = async { 22 | x = 1 23 | } 24 | assert(x == 0) 25 | c.runToFuture.map{r => 26 | assert(x == 1 ) 27 | } 28 | } 29 | 30 | 31 | test("monix: make sure that exception is catched inside async expression ") { 32 | //implicit val printCode = cps.macroFlags.PrintCode 33 | val c1 = async { 34 | throw new RuntimeException("AAA") 35 | } 36 | val c2 = async { 37 | var x = 0 38 | var y = 0 39 | try { 40 | await(c1) 41 | x = 1 42 | }catch{ 43 | case ex: RuntimeException => 44 | assert(ex.getMessage()=="AAA") 45 | y = 2 46 | } 47 | assert(x == 0) 48 | assert(y == 2) 49 | } 50 | c2.runToFuture 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /monix/shared/src/test/scala/cps/monixtest/StupidFizzBuzzSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import scala.language.implicitConversions 4 | import scala.concurrent.duration.* 5 | 6 | import monix.execution.atomic.* 7 | import monix.eval.* 8 | 9 | import cps.* 10 | import cps.monads.monix.given 11 | 12 | import munit.* 13 | 14 | class StupidFizzBuzzSuite extends FunSuite { 15 | 16 | import monix.execution.Scheduler.Implicits.global 17 | 18 | test("make sure that FizBuzz run N times in async loop") { 19 | val run = async { 20 | val logger = ToyLogger.make() 21 | val ctr = Atomic(0) 22 | while { 23 | val v = ctr.get() 24 | await(logger.log(v.toString)) 25 | if v % 3 == 0 then 26 | await(logger.log("fizz")) 27 | if v % 5 == 0 then 28 | await(logger.log("buzz")) 29 | ctr += 1 30 | v < 10 31 | } do () 32 | await(logger.all()) 33 | } 34 | run.map{logs => 35 | assert(logs(0)=="0") 36 | assert(logs(6)=="fizz") 37 | //println(logs) 38 | }.runToFuture 39 | } 40 | 41 | test("make sure that FizBuzz run N times in async loop [automatic coloring removed]") { 42 | import cps.syntax.{*,given} 43 | val run = async { 44 | val logger = ToyLogger.make() 45 | val ctr = Atomic(0) 46 | while { 47 | val v = ctr.get() 48 | // ! cause compiler error 49 | await(logger.log(v.toString)) 50 | if v % 3 == 0 then 51 | ! logger.log("fizz") 52 | if v % 5 == 0 then 53 | ! logger.log("buzz") 54 | ctr += 1 55 | v < 10 56 | } do () 57 | await(logger.all()) 58 | } 59 | run.map{logs => 60 | assert(logs(0)=="0") 61 | assert(logs(6)=="fizz") 62 | //println(logs) 63 | }.runToFuture 64 | } 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /monix/shared/src/test/scala/cps/monixtest/ToyLogger.scala: -------------------------------------------------------------------------------- 1 | package cps.monixtest 2 | 3 | import monix.execution.* 4 | import monix.eval.* 5 | 6 | 7 | class ToyLogger: 8 | 9 | 10 | var lines = Vector.empty[String] 11 | lazy val localScheduler = SingleThreadScheduler() 12 | 13 | 14 | def log(message: String):Task[Unit] = 15 | Task{lines = lines :+ message}.executeOn(localScheduler, forceAsync=true) 16 | 17 | def all(): Task[Vector[String]] = 18 | Task(lines).executeOn(localScheduler, forceAsync = true) 19 | 20 | 21 | object ToyLogger: 22 | 23 | def make(): ToyLogger = 24 | new ToyLogger() 25 | 26 | -------------------------------------------------------------------------------- /probability-monad/src/main/scala/cps/monads/probability/DistributionCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.probability 2 | 3 | import probability_monad.* 4 | import cps.* 5 | 6 | import scala.util.Try 7 | 8 | given DistributionCpsMonad: CpsTryMonad[Distribution] with CpsMonadInstanceContext[Distribution] with { 9 | 10 | def pure[A](a:A): Distribution[A] = 11 | Distribution.always(a) 12 | 13 | def map[A,B](fa:Distribution[A])(f:A=>B):Distribution[B] = 14 | fa.map(f) 15 | 16 | def flatMap[A,B](fa:Distribution[A])(f:A=>Distribution[B]):Distribution[B] = 17 | fa.flatMap(f) 18 | 19 | def error[A](e:Throwable): Distribution[A] = 20 | new Distribution[A] { 21 | override def get = { throw e } 22 | } 23 | 24 | override def mapTry[A,B](fa:Distribution[A])(f:Try[A]=>B): Distribution[B] = 25 | new MapTryDistribution(fa,f) 26 | 27 | def flatMapTry[A,B](fa:Distribution[A])(f:Try[A]=>Distribution[B]): Distribution[B] = 28 | new FlatMapTryDistribution(fa,f) 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /probability-monad/src/main/scala/probability_monad/TryDistributions.scala: -------------------------------------------------------------------------------- 1 | package probability_monad 2 | 3 | import scala.util.Try 4 | 5 | 6 | class MapTryDistribution[A,B](fa:Distribution[A],f:Try[A] => B) extends Distribution[B] { 7 | 8 | override def get: B = { 9 | f(Try(fa.get)) 10 | } 11 | 12 | } 13 | 14 | class FlatMapTryDistribution[A,B](fa:Distribution[A],f:Try[A] => Distribution[B]) extends Distribution[B] { 15 | 16 | override def get: B = { 17 | f(Try(fa.get)).get 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /probability-monad/src/test/scala/cps/monads/probability/ProbabilityExamplesSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.probability 2 | 3 | 4 | import cps.* 5 | import probability_monad.* 6 | import Distribution.* 7 | 8 | import munit.* 9 | 10 | class ProbabilityExamplesSuite extends FunSuite { 11 | 12 | case class Trial(haveFairCoin: Boolean, flips: List[Coin]) 13 | 14 | def bayesianCoin(nFlips: Int): Distribution[Trial] = reify[Distribution] { 15 | val haveFairCoin = reflect(tf()) 16 | val myCoin = if (haveFairCoin) coin else biasedCoin(0.9) 17 | val flips = reflect(myCoin.repeat(nFlips)) 18 | Trial(haveFairCoin, flips) 19 | } 20 | 21 | test("basic coin test") { 22 | val p = bayesianCoin(5).filter(_.flips.forall(_ == H)).pr(_.haveFairCoin) 23 | //println(s"p=$p") 24 | assert(p < 0.1) 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.2 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1") 2 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") 3 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.1") 4 | //addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.11.3") 5 | //TODO: enableafter munit scala-native support 6 | //addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.0") 7 | //addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.0") 8 | -------------------------------------------------------------------------------- /publish.sbt: -------------------------------------------------------------------------------- 1 | 2 | credentials += Credentials(Path.userHome / ".sbt" / "central_sonatype_credentials") 3 | 4 | ThisBuild / organization := "io.github.dotty-cps-async" 5 | ThisBuild / organizationName := "dotty-cps-async" 6 | ThisBuild / organizationHomepage := Some(url("https://github.com/dotty-cps-async")) 7 | 8 | ThisBuild / scmInfo := Some( 9 | ScmInfo( 10 | url("https://github.com/dotty-cps-async/cps-async-connect"), 11 | "scm:git@github.com:rssh/cps-async-connect.git" 12 | ) 13 | ) 14 | 15 | 16 | ThisBuild / developers := List( 17 | Developer( 18 | id = "rssh", 19 | name = "Ruslan Shevchenko", 20 | email = "ruslan@shevchenko.kiev.ua", 21 | url = url("https://github.com/rssh") 22 | ) 23 | ) 24 | 25 | 26 | ThisBuild / description := "cps-async-connect: integration of dotty-cps-async with effect stacks" 27 | ThisBuild / licenses := List("Apache 2" -> new URL("http://www.apache.org/licenses/LICENSE-2.0.txt")) 28 | ThisBuild / homepage := Some(url("https://github.com/dotty-cps-async/cps-async-connect")) 29 | 30 | ThisBuild / pomIncludeRepository := { _ => false } 31 | ThisBuild / publishMavenStyle := true 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /scalaz/shared/src/main/scala/cps/monads/scalaz/ScalazIOCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.scalaz 2 | 3 | import cps._ 4 | import scalaz._ 5 | import scalaz.effect._ 6 | 7 | import scala.util.Try 8 | 9 | given scalazIO: CpsTryMonad[IO] with CpsMonadInstanceContext[IO] with 10 | 11 | type F[T] = IO[T] 12 | 13 | def pure[A](a:A): IO[A] = IO(a) 14 | 15 | def map[A,B](fa: IO[A])(f: A=>B): IO[B] = 16 | fa.map(f) 17 | 18 | def flatMap[A,B](fa: IO[A])(f: A=>IO[B]): IO[B] = 19 | fa.flatMap(f) 20 | 21 | def error[A](e: Throwable): IO[A] = 22 | IO.throwIO[A](e) 23 | 24 | def flatMapTry[A,B](fa: IO[A])(f: Try[A]=>IO[B]) = 25 | fa.catchLeft.flatMap{ 26 | _.fold( e => f(scala.util.Failure(e)), a => f(scala.util.Success(a)) ) 27 | } 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /scalaz/shared/src/test/scala/cps/monads/scalaz/IOPrintSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.scalaz 2 | 3 | import cps._ 4 | import munit._ 5 | 6 | import scalaz.effect.IO 7 | 8 | class IOPrintSute extends FunSuite { 9 | 10 | test("simple async/await over scalaz IO") { 11 | val program = async { 12 | val line = "line" 13 | await(IO.putStrLn(line)) 14 | } 15 | program.unsafePerformIO() 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /stream-akka/src/main/scala/cps/stream/akka/AkkaStreamEmitAbsorber.scala: -------------------------------------------------------------------------------- 1 | package cps.stream.akka 2 | 3 | import scala.concurrent.* 4 | import akka.NotUsed 5 | import akka.stream.* 6 | import akka.stream.scaladsl.* 7 | 8 | 9 | import cps.* 10 | import cps.monads.{*,given} 11 | import cps.stream.{*,given} 12 | 13 | 14 | given AkkaStreamEmitAbsorber[T](using ExecutionContext, Materializer): BaseUnfoldCpsAsyncEmitAbsorber[Source[T,NotUsed],Future, FutureContext, T] with 15 | 16 | override type Element = T 17 | 18 | def asSync(fs: Future[Source[T,NotUsed]]):Source[T,NotUsed] = 19 | Source.futureSource(fs).preMaterialize()._2 20 | 21 | def unfold[S](s0:S)(f:S => Future[Option[(T,S)]]): Source[T, NotUsed] = 22 | Source.unfoldAsync[S,T](s0)((s) => f(s).map(_.map{ case (x,y) => (y,x) }) ) 23 | 24 | -------------------------------------------------------------------------------- /stream-akka/src/test/scala/cps/akkastreamtest/BasicGeneratorSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.akkastramtest 2 | 3 | import scala.concurrent.* 4 | import scala.concurrent.ExecutionContext.Implicits.global 5 | 6 | 7 | import akka.* 8 | import akka.actor.ActorSystem 9 | import akka.stream.* 10 | import akka.stream.scaladsl.* 11 | 12 | import cps.* 13 | import cps.monads.given 14 | import cps.stream.akka.given 15 | 16 | 17 | import munit.* 18 | 19 | 20 | class BasicGeneratorSuite extends FunSuite { 21 | 22 | val N = 10000 23 | 24 | given system: ActorSystem = ActorSystem("BasicGeneratorSuite") 25 | 26 | test("simple loop in akka-stream") { 27 | 28 | val source = asyncStream[Source[Int,NotUsed]] { out => 29 | for(i <- 1 to N) { 30 | out.emit(i) 31 | } 32 | } 33 | 34 | 35 | source.runWith(Sink.seq).map{ v => 36 | assert(v.length == N) 37 | assert(v(0)==1) 38 | assert(v(1)==2) 39 | } 40 | 41 | } 42 | 43 | test("exception should break loop: akka-stream") { 44 | val source = asyncStream[Source[Int,NotUsed]] { out => 45 | for(i <- 1 to N) { 46 | if (i == N/2) then 47 | throw new RuntimeException("bye") 48 | out.emit(i) 49 | } 50 | } 51 | 52 | val res = source.runWith(Sink.seq) 53 | 54 | res.failed.map(ex => assert(ex.getMessage()=="bye")) 55 | 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /stream-fs2/shared/src/main/scala/cpsfs2/Fs2AsyncEmitter.scala: -------------------------------------------------------------------------------- 1 | package cps.stream.fs2stream 2 | 3 | import cps.* 4 | import cps.stream.* 5 | import scala.concurrent.* 6 | 7 | given fs2EmitAbsorber[F[_],C <: CpsMonadContext[F],T](using ExecutionContext, CpsConcurrentMonad.Aux[F,C]): BaseUnfoldCpsAsyncEmitAbsorber[fs2.Stream[F,T],F,C,T] with 8 | 9 | override type Element = T 10 | 11 | def asSync(fs: F[fs2.Stream[F,T]]): fs2.Stream[F,T] = 12 | fs2.Stream.force(fs) 13 | 14 | def unfold[S](s0:S)(f:S => F[Option[(T,S)]]): fs2.Stream[F,T] = 15 | fs2.Stream.unfoldEval[F,S,T](s0)(f) 16 | 17 | -------------------------------------------------------------------------------- /stream-fs2/shared/src/test/scala/cpsfs2test/BasicGeneratorSuite.scala: -------------------------------------------------------------------------------- 1 | package cpsfs2test 2 | 3 | import scala.concurrent.* 4 | 5 | import cps.* 6 | import cps.monads.catsEffect.{*,given} 7 | import cps.stream.* 8 | import cps.stream.fs2stream.{*,given} 9 | 10 | import munit.* 11 | import munit.CatsEffectSuite 12 | 13 | import cats.effect.* 14 | 15 | 16 | 17 | class BasicGeneratorSuite extends CatsEffectSuite { 18 | 19 | val N = 10000 20 | 21 | test("simple loop in fs2") { 22 | given ExecutionContext = ExecutionContext.global 23 | val stream = asyncStream[fs2.Stream[IO,Int]] { out => 24 | for(i <- 1 to N) { 25 | out.emit(i) 26 | } 27 | } 28 | 29 | val fVector = stream.compile.toVector 30 | 31 | fVector.map{ v => 32 | assert(v.length == N) 33 | assert(v(0)==1) 34 | assert(v(1)==2) 35 | } 36 | } 37 | 38 | test("exception should break loop: fs2") { 39 | given ExecutionContext = ExecutionContext.global 40 | val stream = asyncStream[fs2.Stream[IO,Int]] { out => 41 | for(i <- 1 to N) { 42 | if (i == N/2) then 43 | throw new RuntimeException("bye") 44 | out.emit(i) 45 | } 46 | } 47 | 48 | // shouls throw bye 49 | val ioVector = stream.compile.toVector 50 | 51 | ioVector.map(_ => "normal").handleError{ v => 52 | v.getMessage() 53 | }.map{ s => 54 | assert( s == "bye" ) 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /stream-pekko/src/main/scala/cps/stream/pekko/PekkoStreamEmitAbsorber.scala: -------------------------------------------------------------------------------- 1 | package cps.stream.pekko 2 | 3 | import scala.concurrent.* 4 | import org.apache.pekko 5 | import pekko.NotUsed 6 | import pekko.stream.* 7 | import pekko.stream.scaladsl.* 8 | 9 | 10 | import cps.* 11 | import cps.monads.{*,given} 12 | import cps.stream.{*,given} 13 | 14 | 15 | given PekkoStreamEmitAbsorber[T](using ExecutionContext, Materializer): BaseUnfoldCpsAsyncEmitAbsorber[Source[T,NotUsed],Future, FutureContext, T] with 16 | 17 | override type Element = T 18 | 19 | def asSync(fs: Future[Source[T,NotUsed]]):Source[T,NotUsed] = 20 | Source.futureSource(fs).preMaterialize()._2 21 | 22 | def unfold[S](s0:S)(f:S => Future[Option[(T,S)]]): Source[T, NotUsed] = 23 | Source.unfoldAsync[S,T](s0)((s) => f(s).map(_.map{ case (x,y) => (y,x) }) ) 24 | 25 | -------------------------------------------------------------------------------- /stream-pekko/src/test/scala/cps/pekkostreamtest/BasicGeneratorSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.pekkostreamtest 2 | 3 | import scala.concurrent.* 4 | import scala.concurrent.ExecutionContext.Implicits.global 5 | 6 | import org.apache.pekko.* 7 | import org.apache.pekko.actor.ActorSystem 8 | import org.apache.pekko.stream.* 9 | import org.apache.pekko.stream.scaladsl.* 10 | 11 | import cps.* 12 | import cps.monads.given 13 | import cps.stream.pekko.given 14 | 15 | 16 | import munit.* 17 | 18 | 19 | class BasicGeneratorSuite extends FunSuite { 20 | 21 | val N = 10000 22 | 23 | given system: ActorSystem = ActorSystem("BasicGeneratorSuite") 24 | 25 | test("simple loop in pekko-stream") { 26 | 27 | val source = asyncStream[Source[Int,NotUsed]] { out => 28 | for(i <- 1 to N) { 29 | out.emit(i) 30 | } 31 | } 32 | 33 | 34 | source.runWith(Sink.seq).map{ v => 35 | assert(v.length == N) 36 | assert(v(0)==1) 37 | assert(v(1)==2) 38 | } 39 | 40 | } 41 | 42 | test("exception should break loop: pekko-stream") { 43 | val source = asyncStream[Source[Int,NotUsed]] { out => 44 | for(i <- 1 to N) { 45 | if (i == N/2) then 46 | throw new RuntimeException("bye") 47 | out.emit(i) 48 | } 49 | } 50 | 51 | val res = source.runWith(Sink.seq) 52 | 53 | res.failed.map(ex => assert(ex.getMessage()=="bye")) 54 | 55 | } 56 | 57 | 58 | } -------------------------------------------------------------------------------- /zio/shared/src/main/scala/cps/monads/zio/ThrowableAdapter.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import zio._ 4 | 5 | 6 | case class ZIOErrorAdapter[E](e:E) extends RuntimeException 7 | 8 | object GenericThrowableAdapter: 9 | 10 | def toThrowable[E](e:E): Throwable = 11 | e match 12 | case eth: Throwable => eth 13 | case _ => new ZIOErrorAdapter(e) 14 | 15 | def fromThrowable[R,E,A](e:Throwable): ZIO[R,E,A] = 16 | e match 17 | case ZIOErrorAdapter(e1) => ZIO.fail(e1.asInstanceOf[E]) 18 | case other => ZIO.fail( other.asInstanceOf[E] ) 19 | 20 | 21 | /* 22 | // TODO: rething, mb change to static check 23 | given throwableForThrowable[R, X <: Throwable]: ThrowableAdapter[R, X] with 24 | def toThrowable(e: X): Throwable = e 25 | 26 | def fromThrowable[A](e: Throwable): ZIO[R, X, A] = 27 | ZIO.fail(e.asInstanceOf[X]) 28 | */ 29 | -------------------------------------------------------------------------------- /zio/shared/src/main/scala/cps/monads/zio/Using.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import cps.* 4 | import zio.* 5 | 6 | /** 7 | * pseudo-synchronious variant of `use` for using inside async block. 8 | */ 9 | extension [R,E,A](r: ZManaged[R,E,A])(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]]) 10 | 11 | transparent inline def useOn[B](inline f: A=>B)(using CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 12 | await(r.use(a => m.pure(f(a)) )) 13 | 14 | /** 15 | * using ZManaged resource and close `r` after f will be finished. 16 | * Shpuld be used inside async block, 'f' can contains awaits. 17 | */ 18 | extension (zm: ZManaged.type) 19 | 20 | transparent inline def using[R,E,A,B](r:ZManaged[R,E,A])(inline f: A=>B)(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]], mc:CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 21 | await(r.use(a => m.pure(f(a)) )) 22 | 23 | transparent inline def using[R, E, A1, A2, B](r1:ZManaged[R,E,A1], r2:ZManaged[R,E,A2])(inline f: (A1,A2)=>B)(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]], mc:CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 24 | await(r1.use(a1 => r2.use(a2 => m.pure(f(a1,a2))))) 25 | 26 | transparent inline def using[R, E, A1, A2, A3, B](r1:ZManaged[R,E,A1], r2:ZManaged[R,E,A2], r3: ZManaged[R,E,A3])(inline f: (A1,A2,A3)=>B)(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]], mc:CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 27 | await(r1.use(a1 => r2.use(a2 => r3.use(a3 => m.pure(f(a1,a2,a3)))))) 28 | 29 | -------------------------------------------------------------------------------- /zio/shared/src/main/scala/cps/monads/zio/ZIOCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import cps._ 4 | import cps.macros._ 5 | import zio._ 6 | import scala.util._ 7 | import scala.concurrent._ 8 | 9 | 10 | 11 | class ZIOCpsMonad[R, E] extends CpsConcurrentEffectMonad[[X]=>>ZIO[R,E,X]] with CpsMonadInstanceContext[[X]=>>ZIO[R,E,X]]: 12 | 13 | type F[T] = ZIO[R,E,T] 14 | 15 | type Spawned[T] = Fiber[E,T] 16 | 17 | def pure[A](x:A):ZIO[R,E,A] = ZIO.succeed(x) 18 | 19 | def map[A,B](fa: F[A])(f: A=>B): F[B] = 20 | fa.map(f) 21 | 22 | def flatMap[A,B](fa: F[A])(f: A=> F[B]): F[B] = 23 | fa.flatMap(f) 24 | 25 | def error[A](e: Throwable): F[A] = 26 | e match 27 | case ZIOErrorAdapter(e1) => ZIO.fail(e1.asInstanceOf[E]) 28 | case other => ZIO.fail(other.asInstanceOf[E]) 29 | 30 | def flatMapTry[A, B](fa: F[A])(f: util.Try[A] => F[B]): F[B] = 31 | fa.foldM( 32 | e => f(Failure(GenericThrowableAdapter.toThrowable(e))), 33 | a => f(Success(a)) 34 | ) 35 | 36 | def adoptCallbackStyle[A](source: (util.Try[A] => Unit) => Unit): F[A] = 37 | def adoptZIOCallback(zioCallback: ZIO[R,E,A]=>Unit): Try[A]=>Unit = { 38 | case Failure(ex) => zioCallback(error(ex)) 39 | case Success(a) => zioCallback(ZIO.succeed(a)) 40 | } 41 | ZIO.effectAsync[R,E,A] { cb => 42 | source(adoptZIOCallback(cb)) 43 | } 44 | 45 | def spawnEffect[A](op: =>F[A]):ZIO[R,E,Spawned[A]] = 46 | op.fork 47 | 48 | def join[A](op: Fiber[E,A]) = 49 | op.join 50 | 51 | def tryCancel[A](op: Fiber[E,A]):F[Unit] = 52 | op.interrupt.map(_ => ()) 53 | 54 | 55 | 56 | //object TaskCpsMonad extends ZIOCpsMonad[Any,Throwable] 57 | // 58 | //given CpsConcurrentEffectMonad[Task] = TaskCpsMonad 59 | 60 | given zioCpsMonad[R,E]: ZIOCpsMonad[R,E] = ZIOCpsMonad[R,E] 61 | 62 | transparent inline def asyncZIO[R,E](using CpsConcurrentEffectMonad[[X]=>>ZIO[R,E,X]]): Async.InferAsyncArg[[X]=>>ZIO[R,E,X],CpsMonadInstanceContextBody[[X]=>>ZIO[R,E,X]]] = 63 | new cps.macros.Async.InferAsyncArg 64 | 65 | transparent inline def asyncRIO[R]: Async.InferAsyncArg[[X]=>>RIO[R,X], CpsMonadInstanceContextBody[[X]=>>ZIO[R,Throwable,X]]] = 66 | new Async.InferAsyncArg(using ZIOCpsMonad[R, Throwable]) 67 | 68 | 69 | given zioToZio[R1,R2<:R1,E1,E2>:E1]: CpsMonadConversion[[T] =>> ZIO[R1,E1,T], [T]=>>ZIO[R2,E2,T]] with 70 | 71 | def apply[T](ft:ZIO[R1,E1,T]): ZIO[R2,E2,T]= ft 72 | 73 | 74 | //given zioToRio[R]: CpsMonadConversion[[T] =>> ZIO[Nothing,Any,T], [T]=>>RIO[R,T]] with 75 | // 76 | // def apply[T](ft:ZIO[Nothing,Any,T]): RIO[R,T] = 77 | // val r1 = ft.foldM( 78 | // e => { 79 | // ZIO.fail[Throwable](GenericThrowableAdapter.toThrowable(e)) 80 | // }, 81 | // v => ZIO.succeed(v) 82 | // ) 83 | // val r2: RIO[R,T] = r1.asInstanceOf[ZIO[R,Throwable,T]] 84 | // r2 85 | 86 | 87 | 88 | given futureZIOConversion[R,E](using zio.Runtime[R]): 89 | CpsMonadConversion[[T]=>>ZIO[R,E,T],Future] with 90 | 91 | def apply[T](ft:ZIO[R,E,T]): Future[T] = 92 | summon[Runtime[R]].unsafeRunToFuture(ft.mapError(e => GenericThrowableAdapter.toThrowable(e))) 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /zio/shared/src/main/scala/cps/monads/zio/ZManagedCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import cps._ 4 | import cps.macros._ 5 | import zio._ 6 | import scala.util._ 7 | import scala.concurrent._ 8 | 9 | 10 | /** 11 | * CpsMonad which encapsulate effects with automatic resource management. 12 | * 13 | * Example of usage: 14 | * ``` 15 | * asyncRManaged[R] { 16 | * val input = FileChannel.open(inputPath) 17 | * val output = FileChannel.open(outputPath) 18 | * input.transformTo(0,Long.MaxValue,output) 19 | * } 20 | * ``` 21 | **/ 22 | class ZManagedCpsMonad[R, E] extends CpsTryMonad[[X]=>>ZManaged[R,E,X]] with CpsMonadInstanceContext[[X]=>>ZManaged[R,E,X]]: 23 | 24 | type F[T] = ZManaged[R,E,T] 25 | 26 | def pure[A](x:A):ZManaged[R,E,A] = ZManaged.succeed(x) 27 | 28 | def map[A,B](fa: F[A])(f: A=>B): F[B] = 29 | fa.map(f) 30 | 31 | def flatMap[A,B](fa: F[A])(f: A=> F[B]): F[B] = 32 | fa.flatMap(f) 33 | 34 | def error[A](e: Throwable): F[A] = 35 | ZManaged.fromEffect(GenericThrowableAdapter.fromThrowable(e)) 36 | 37 | def flatMapTry[A, B](fa: F[A])(f: util.Try[A] => F[B]): F[B] = 38 | fa.foldM( 39 | e => f(Failure(GenericThrowableAdapter.toThrowable(e))), 40 | a => f(Success(a)) 41 | ) 42 | 43 | 44 | 45 | 46 | object TaskManagedCpsMonad extends ZManagedCpsMonad[Any,Throwable] 47 | 48 | given CpsTryMonad[TaskManaged] = TaskManagedCpsMonad 49 | 50 | 51 | given zManagedCpsMonad[R,E]: ZManagedCpsMonad[R,E] = ZManagedCpsMonad[R,E] 52 | 53 | transparent inline def asyncZManaged[R,E](using CpsTryMonad[[X]=>>ZManaged[R,E,X]]): Async.InferAsyncArg[[X]=>>ZManaged[R,E,X],CpsMonadInstanceContextBody[[X]=>>ZManaged[R,E,X]]] = 54 | new cps.macros.Async.InferAsyncArg 55 | 56 | transparent inline def asyncRManaged[R]: Async.InferAsyncArg[[X]=>>RManaged[R,X],CpsMonadInstanceContextBody[[X]=>>ZManaged[R,Throwable,X]]] = 57 | new Async.InferAsyncArg(using ZManagedCpsMonad[R, Throwable]) 58 | 59 | 60 | given zioToZManaged[R1,R2<:R1,E1,E2>:E1]: 61 | CpsMonadConversion[[T] =>> ZIO[R1,E1,T], 62 | [T]=>> ZManaged[R2,E2,T]] with 63 | 64 | def apply[T](ft:ZIO[R1,E1,T]): ZManaged[R2,E2,T]= 65 | ZManaged.fromEffect[R2,E2,T](ft) 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /zio/shared/src/main/scala/cps/stream/zio/ZStreamEmitAbsorber.scala: -------------------------------------------------------------------------------- 1 | package cps.stream.zio 2 | 3 | import zio.* 4 | import zio.stream.* 5 | 6 | 7 | import cps.{*,given} 8 | import cps.monads.zio.{*,given} 9 | import cps.stream.{*,given} 10 | import scala.concurrent.* 11 | 12 | 13 | given ZStreamEmitAbsorber[R,E,O](using ExecutionContext): BaseUnfoldCpsAsyncEmitAbsorber[ZStream[R,E,O], [X]=>>ZIO[R,E,X], CpsMonadInstanceContextBody[[X]=>>ZIO[R,E,X]], O] with 14 | 15 | override type Element = O 16 | 17 | override def asSync(fs:ZIO[R,E,ZStream[R,E,O]]):ZStream[R,E,O] = 18 | ZStream.unwrap(fs) 19 | 20 | def unfold[S](s0:S)(f:S => ZIO[R,E,Option[(O,S)]]): ZStream[R,E,O] = 21 | ZStream.unfoldM[R,E,O,S](s0)(f) 22 | 23 | -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/BasicGeneratorSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import scala.concurrent.* 4 | 5 | import zio.* 6 | import zio.stream.* 7 | import cps.* 8 | import cps.monads.given 9 | import cps.monads.zio.{*,given} 10 | import cps.stream.zio.{*,given} 11 | 12 | 13 | import munit.* 14 | 15 | 16 | class BasicGeneratorSuite extends FunSuite { 17 | 18 | val N = 10000 19 | 20 | given ExecutionContext = ExecutionContext.global 21 | 22 | test("simple loop in ZStream") { 23 | 24 | val stream = asyncStream[Stream[Throwable,Int]] { out => 25 | for(i <- 1 to N) { 26 | out.emit(i) 27 | } 28 | } 29 | 30 | val res = stream.fold(0)(_ + _) 31 | 32 | Runtime.default.unsafeRunToFuture(res).map(x => 33 | assert(x == (1 to N).sum) 34 | ) 35 | 36 | } 37 | 38 | test("exception should break loop: ZStream") { 39 | val stream = asyncStream[Stream[Throwable, Int]] { out => 40 | for(i <- 1 to N) { 41 | if (i == N/2) then 42 | throw new RuntimeException("bye") 43 | out.emit(i) 44 | } 45 | } 46 | 47 | val res = stream.fold(0)(_ + _) 48 | 49 | Runtime.default.unsafeRunToFuture(res).failed.map(ex => assert(ex.getMessage()=="bye")) 50 | 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/ConcurrentSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import zio._ 4 | import munit._ 5 | 6 | import scala.concurrent._ 7 | import scala.concurrent.duration._ 8 | 9 | import cps._ 10 | import cps.monads.given 11 | import cps.monads.zio.{given,*} 12 | import java.util.concurrent.atomic.AtomicInteger 13 | 14 | 15 | import scala.concurrent.ExecutionContext.Implicits.global 16 | 17 | class ConcurrentSuite extends munit.FunSuite { 18 | 19 | test("basic concurrent test") { 20 | 21 | val run = async[Task] { 22 | val m = summon[CpsConcurrentEffectMonad[Task]] 23 | val x = new AtomicInteger(0) 24 | val fiber1 = await(m.spawnEffect{ 25 | Task.effect{ x.incrementAndGet() } 26 | }) 27 | val fiber2 = await(m.spawnEffect{ 28 | Task.effect{ x.incrementAndGet() } 29 | }) 30 | val y1 = await(m.join(fiber1)) 31 | val y2 = await(m.join(fiber2)) 32 | assert(x.get() == 2 ) 33 | } 34 | 35 | } 36 | 37 | 38 | } -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/FutureInteropSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio._ 5 | import munit._ 6 | 7 | import scala.concurrent._ 8 | import scala.concurrent.duration._ 9 | 10 | import cps._ 11 | import cps.monads.given 12 | import cps.monads.zio.{given,*} 13 | 14 | import scala.concurrent.ExecutionContext.Implicits.global 15 | 16 | class FutureInteropSuite extends munit.FunSuite { 17 | 18 | class FutureBasedAPI { 19 | 20 | def getX: Future[Int] = Future successful 3 21 | 22 | } 23 | 24 | 25 | class ZIOBasedAPI { 26 | 27 | def getY: Task[Int] = ZIO.succeed(2) 28 | 29 | } 30 | 31 | test("make sure that ZIO async can adopt Future in Task") { 32 | val futureApi = new FutureBasedAPI() 33 | val zioApi = new ZIOBasedAPI() 34 | val run = async[Task] { 35 | val x = await(futureApi.getX) 36 | val y = await(zioApi.getY) 37 | assert(x + y == 5) 38 | } 39 | Runtime.default.unsafeRunToFuture(run) 40 | } 41 | 42 | test("make sure that ZIO async can adopt Future in RIO") { 43 | class Apis { 44 | val futureApi = new FutureBasedAPI() 45 | val zioApi = new ZIOBasedAPI() 46 | } 47 | val program = asyncRIO[Apis] { 48 | val futureApi = await(ZIO.access[Apis](_.futureApi)) 49 | val x = await(futureApi.getX) 50 | val y = await(ZIO.accessM[Apis](_.zioApi.getY)) 51 | assert(x + y == 5) 52 | } 53 | Runtime.default.unsafeRunToFuture(program.provide(new Apis)) 54 | } 55 | 56 | test("make sure that Future async can adopt ZIO with given Runtime") { 57 | val futureApi = new FutureBasedAPI() 58 | val zioApi = new ZIOBasedAPI() 59 | given zio.Runtime[ZEnv] = zio.Runtime.default 60 | val run = async[Future] { 61 | val x = await(futureApi.getX) 62 | val y = await(zioApi.getY) 63 | assert(x + y == 5) 64 | } 65 | run 66 | } 67 | 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/LazyEffectSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio.* 5 | import munit.* 6 | import concurrent.duration.* 7 | 8 | import cps.* 9 | import cps.monads.zio.{given,*} 10 | 11 | class LazyEffectSuite extends FunSuite { 12 | 13 | import concurrent.ExecutionContext.Implicits.global 14 | 15 | test("zio: make sure that evaluation of async expression is delayed") { 16 | 17 | implicit val printCode = cps.macros.flags.PrintCode 18 | var x = 0 19 | val c = async[Task] { 20 | x = 1 21 | } 22 | assert(x == 0) 23 | Runtime.default.unsafeRunToFuture(c).map{ r => 24 | assert(x == 1 ) 25 | } 26 | 27 | } 28 | 29 | test("zio: make sure that exception is catched inside async expression ") { 30 | //implicit val printCode = cps.macroFlags.PrintCode 31 | val c1 = async[Task] { 32 | throw new RuntimeException("AAA") 33 | } 34 | val c2 = async[Task] { 35 | var x = 0 36 | var y = 0 37 | try { 38 | await(c1) 39 | x = 1 40 | }catch{ 41 | case ex: RuntimeException => 42 | assert(ex.getMessage()=="AAA") 43 | y = 2 44 | } 45 | assert(x == 0) 46 | assert(y == 2) 47 | } 48 | Runtime.default.unsafeRunToFuture(c2) 49 | } 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/ResourceSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio.* 5 | import munit.* 6 | import scala.concurrent.duration.* 7 | import scala.collection.mutable.ArrayBuffer 8 | 9 | import cps.* 10 | import cps.monads.zio.{given,*} 11 | 12 | 13 | class WriterEmu { 14 | 15 | var isClosed: Boolean = false 16 | val messages: ArrayBuffer[String] = ArrayBuffer.empty 17 | 18 | def write(message:String): Unit = { 19 | if (!isClosed) 20 | messages.append(message) 21 | else 22 | throw new IllegalStateException("resource is already closed") 23 | } 24 | 25 | def close(): Unit = 26 | isClosed = true 27 | 28 | } 29 | 30 | 31 | 32 | class ResourceSuite extends FunSuite { 33 | 34 | import concurrent.ExecutionContext.Implicits.global 35 | 36 | 37 | def makeWriterEmu(): TaskManaged[WriterEmu] = 38 | ZManaged.make(ZIO.effect(new WriterEmu()))(a => ZIO.effectTotal(a.close())) 39 | 40 | test("using ZManaged") { 41 | //implicit val printCode = cps.macros.flags.PrintCode 42 | val prg = async[Task] { 43 | val r = makeWriterEmu() 44 | val ctr = await(Ref.make(0)) 45 | ZManaged.using(r) { a => 46 | val c0 = await(ctr.get) 47 | a.write(s"AAA-${c0}") 48 | await(ctr.update(_ + 1)) 49 | val c1 = await(ctr.get) 50 | a.write(s"BBB-${c1}") 51 | (a, a.messages.toList, a.isClosed) 52 | } 53 | } 54 | Runtime.default.unsafeRunToFuture(prg).map{ 55 | (a, messages, isClosedInside) => 56 | //println(s"a = $a") 57 | //println(s"messages = $messages") 58 | assert(a.isClosed) 59 | assert(!isClosedInside) 60 | } 61 | 62 | } 63 | 64 | } 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/StupidFizzBuzzSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio.* 5 | import munit.* 6 | import concurrent.duration.* 7 | 8 | import cps.* 9 | import cps.monads.zio.{given,*} 10 | 11 | 12 | class StupidFizzBuzzSuite extends FunSuite { 13 | 14 | import concurrent.ExecutionContext.Implicits.global 15 | 16 | test("make sure that FizBuzz run N times in async loop") { 17 | 18 | val program = asyncRIO[TLogging] { 19 | val ctr = await(Ref.make(0)) 20 | while { 21 | val v = await(ctr.get) 22 | await(TLog.logMsg(v.toString)) 23 | if v % 3 == 0 then 24 | await(TLog.logMsg("fizz")) 25 | if v % 5 == 0 then 26 | await(TLog.logMsg("buzz")) 27 | await(ctr.update(_ + 1)) 28 | v < 10 29 | } do () 30 | await(TLog.lastRecords(20)) 31 | } 32 | 33 | val logService: TLogging.Service = new TLoggingImpl.Service 34 | val r = program.provideLayer( ZLayer.succeed(logService) ) 35 | Runtime.default.unsafeRunToFuture(r).map{ logs => 36 | //println(s"logs=$logs") 37 | assert(logs(0)==TLogging.MsgRecord("0")) 38 | assert(logs(1)==TLogging.MsgRecord("fizz")) 39 | assert(logs(2)==TLogging.MsgRecord("buzz")) 40 | assert(logs(3)==TLogging.MsgRecord("1")) 41 | assert(logs(4)==TLogging.MsgRecord("2")) 42 | assert(logs(5)==TLogging.MsgRecord("3")) 43 | assert(logs(6)==TLogging.MsgRecord("fizz")) 44 | assert(logs(7)==TLogging.MsgRecord("4")) 45 | } 46 | } 47 | 48 | test("minimal [disabled automatic coloring, short syntax also not work on ZIO. ]") { 49 | import cps.syntax.* 50 | 51 | //implicit val printCode = cps.macroFlags.PrintCode 52 | 53 | val program = asyncRIO[TLogging] { 54 | val ctr = await(Ref.make(0)) 55 | val v = await(ctr.get) 56 | await(TLog.logMsg("AAA")) 57 | val records: IndexedSeq[TLogging.LogRecord] = await(TLog.lastRecords(20)) 58 | records 59 | } 60 | val logService: TLogging.Service = new TLoggingImpl.Service 61 | val r = program.provideLayer( ZLayer.succeed(logService) ) 62 | Runtime.default.unsafeRunToFuture(r) 63 | 64 | } 65 | 66 | 67 | test("make sure that FizBuzz run N times in async loop // automatic coloring removed") { 68 | 69 | import cps.syntax.* 70 | 71 | //implicit val printCode = cps.macroFlags.PrintCode 72 | //implicit val printTree = cps.macroFlags.PrintTree 73 | //implicit val debugLevel = cps.macroFlags.DebugLevel(20) 74 | 75 | val program = asyncRIO[TLogging] { 76 | // TODO: find issue, while ctr.get search for option. 77 | // Now, let's do type ascription to force await. 78 | val ctr: Ref[Int] = await(Ref.make(0)) 79 | while { 80 | val v: Int = await(ctr.get) 81 | await(TLog.logMsg(v.toString)) 82 | if v % 3 == 0 then 83 | await(TLog.logMsg("fizz")) 84 | if v % 5 == 0 then 85 | await(TLog.logMsg("buzz")) 86 | await(ctr.update(_ + 1)) 87 | v < 10 88 | } do () 89 | await(TLog.lastRecords(20)) 90 | } 91 | 92 | val logService: TLogging.Service = new TLoggingImpl.Service 93 | val r = program.provideLayer( ZLayer.succeed(logService) ) 94 | Runtime.default.unsafeRunToFuture(r).map{ logs => 95 | //println(s"logs=$logs") 96 | assert(logs(0)==TLogging.MsgRecord("0")) 97 | assert(logs(1)==TLogging.MsgRecord("fizz")) 98 | assert(logs(6)==TLogging.MsgRecord("fizz")) 99 | assert(logs(7)==TLogging.MsgRecord("4")) 100 | } 101 | 102 | } 103 | 104 | 105 | } 106 | -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/TLogging.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import zio._ 4 | 5 | object TLogging { 6 | 7 | sealed class LogRecord 8 | case class OpRecord(op: String) extends LogRecord 9 | case class MsgRecord(msg: String) extends LogRecord 10 | case class ExceptionRecord(ex: Throwable) extends LogRecord 11 | 12 | trait Service { 13 | def lastOp(): Task[Option[String]] 14 | def logOp(op: String): UIO[Unit] 15 | def logMsg(msg: String): UIO[Unit] 16 | def logThrowable(ex: Throwable): Task[Unit] 17 | 18 | def lastRecords(n: Int): Task[IndexedSeq[LogRecord]] 19 | } 20 | 21 | } 22 | 23 | type TLogging = Has[TLogging.Service] 24 | 25 | object TLog { 26 | 27 | def lastOp(): RIO[TLogging, Option[String]] = 28 | ZIO.accessM(_.get.lastOp()) 29 | 30 | def logOp(op:String): RIO[TLogging, Unit] = 31 | ZIO.accessM(_.get.logOp(op)) 32 | 33 | def logMsg(msg:String): RIO[TLogging, Unit] = 34 | ZIO.accessM(_.get.logMsg(msg)) 35 | 36 | def logThrowable(ex: Throwable): RIO[TLogging, Unit] = 37 | ZIO.accessM(_.get.logThrowable(ex)) 38 | 39 | def lastRecords(n: Int): RIO[TLogging, IndexedSeq[TLogging.LogRecord]] = 40 | ZIO.accessM(_.get.lastRecords(n)) 41 | 42 | } 43 | 44 | object TLoggingImpl { 45 | 46 | import TLogging._ 47 | 48 | class Service extends TLogging.Service { 49 | 50 | // will not use multithreading in tests. 51 | var lastOpCache: Option[String] = None 52 | var log: IndexedSeq[LogRecord] = IndexedSeq() 53 | 54 | def lastOp(): Task[Option[String]] = Task(lastOpCache) 55 | 56 | def logOp(op: String): UIO[Unit] = 57 | UIO.apply{ 58 | lastOpCache = Some(op) 59 | log = log :+ OpRecord(op) 60 | } 61 | 62 | def logMsg(msg: String): UIO[Unit] = 63 | UIO.apply{ 64 | log = log :+ MsgRecord(msg) 65 | } 66 | 67 | def logThrowable(ex: Throwable): Task[Unit] = 68 | Task.effect{ 69 | log = log :+ ExceptionRecord(ex) 70 | } 71 | 72 | def lastRecords(n: Int): Task[IndexedSeq[LogRecord]] = 73 | Task{ log.takeRight(n) } 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /zio/shared/src/test/scala/cpszio/TestAsyncZIO.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import cps.* 4 | import cps.monads.zio.{given,*} 5 | 6 | import zio._ 7 | import zio.clock._ 8 | 9 | import munit.* 10 | 11 | 12 | 13 | 14 | class TestAsyncZIO extends munit.FunSuite { 15 | 16 | 17 | test("simple test of asyncZIO-1") { 18 | import scala.concurrent.ExecutionContext.Implicits.global 19 | val program = asyncZIO[TLogging with Clock , Throwable] { 20 | val intRef = await(Ref.make(0)) 21 | await(TLog.logOp("createRef")) 22 | val date = await(currentDateTime) 23 | await(TLog.logOp("getDate")) 24 | await(TLog.lastRecords(10)) 25 | } 26 | val logService: TLogging.Service = new TLoggingImpl.Service 27 | val r = program.provideLayer( ZLayer.succeed(logService) ++ Clock.live ) 28 | Runtime.default.unsafeRunToFuture(r) 29 | .map{ logs => 30 | //println(s"logs=$logs") 31 | assert(logs(0)==TLogging.OpRecord("createRef")) 32 | assert(logs(1)==TLogging.OpRecord("getDate")) 33 | } 34 | } 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /zio2-loom/src/main/scala/cps/monads/zio/ZIO2RuntimeAwaitProvider.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import _root_.zio.* 4 | import cps.* 5 | import cps.monads.zio.{*, given} 6 | 7 | import scala.concurrent.* 8 | import java.util.concurrent.CompletableFuture 9 | import scala.util.control.NonFatal 10 | 11 | 12 | class ZIOCpsRuntimeAwait[R,E](runtime: Runtime[R])(implicit trace:Trace) extends CpsRuntimeAwait[[X] =>> ZIO[R,E,X]] { 13 | 14 | 15 | override def await[A](fa: ZIO[R,E,A])(ctx: CpsTryMonadContext[[X]=>>ZIO[R,E,X]]): A = 16 | val cf = new CompletableFuture[Exit[E,A]]() 17 | // runtime is not a clear analog of Dispatcher, but 18 | Unsafe.unsafely { 19 | runtime.unsafe.runOrFork(fa) match 20 | case Left(fiberRuntime) => 21 | fiberRuntime.unsafe.addObserver((v:Exit[E,A]) => cf.complete(v)) 22 | case Right(v) => cf.complete(v) 23 | } 24 | blocking { 25 | val cfres = try { 26 | cf.get() 27 | } catch 28 | case ex: ExecutionException => 29 | throw ex.getCause() 30 | cfres match 31 | case Exit.Success(a) => 32 | a 33 | case Exit.Failure(cause) => 34 | if (cause.isEmpty) { 35 | throw new ZIOErrorAdapter[String]("ZIO fiber failed with empty list of failures") 36 | } else { 37 | val e = cause.failures.head 38 | val te = if (e.isInstanceOf[Throwable]) then { 39 | e.asInstanceOf[Throwable] 40 | } else { 41 | new ZIOErrorAdapter[E](e) 42 | } 43 | for(se <- cause.failures.tail) { 44 | val tse = if (se.isInstanceOf[Throwable]) then { 45 | se.asInstanceOf[Throwable] 46 | } else { 47 | new ZIOErrorAdapter[E](se) 48 | } 49 | te.addSuppressed(tse) 50 | } 51 | throw te 52 | } 53 | 54 | } 55 | 56 | } 57 | 58 | 59 | class ZIORuntimeAwaitProvider[R,E >: Throwable,A](implicit trace: Trace) extends CpsRuntimeAwaitProvider[[A] =>> ZIO[R,E,A]]{ 60 | 61 | 62 | def inVirtualThread[A](op: => ZIO[R,E,A]): ZIO[R,E,A] = 63 | ZIO.asyncZIO[R,E,A]{ (cb) => 64 | val thread = Thread.startVirtualThread { 65 | () => 66 | try { 67 | val r = op 68 | val _ = cb(r) 69 | } catch { 70 | case NonFatal(ex) => 71 | val _ = cb(ZIO.fail(ex)) 72 | } 73 | } 74 | ZIO.succeed(thread.threadId()) 75 | } 76 | 77 | override def withRuntimeAwait[A](lambda: CpsRuntimeAwait[[X] =>> ZIO[R, E, X]] => ZIO[R,E,A])(using ctx: CpsTryMonadContext[[X]=>>ZIO[R,E,X]]): ZIO[R,E,A] = { 78 | ZIO.runtime[R].flatMap{ runtime => 79 | val runtimeAwait = new ZIOCpsRuntimeAwait[R,E](runtime) 80 | // run in virtual thread for case, when passed lambda is invoked in the main flow of the HO function. 81 | // In such case, start of virtual thread will be faster then determination of blocking in the thread pool 82 | inVirtualThread(lambda(runtimeAwait)) 83 | } 84 | } 85 | 86 | 87 | } 88 | 89 | given zioRuntimeAwaitProvider[R,E >: Throwable,A](using trace: Trace): CpsRuntimeAwaitProvider[[A] =>> ZIO[R,E,A]] = 90 | ZIORuntimeAwaitProvider[R,E,A]() -------------------------------------------------------------------------------- /zio2-loom/src/test/scala/cps/zioloomtest/LoomHOFunSuite.scala: -------------------------------------------------------------------------------- 1 | package cps.zioloomtest 2 | 3 | import zio.* 4 | import cps.* 5 | import cps.monads.zio.{given,*} 6 | import munit.* 7 | 8 | 9 | class LoomHOFunSuite extends FunSuite { 10 | 11 | def incr(x:Int)(using trace:Trace): ZIO[Any,Throwable,Int] = 12 | ZIO.suspend[Any,Int]( ZIO.succeed(x+1))(trace) 13 | 14 | test("check apply function with await as argument to MyList.map") { 15 | val c=async[[X]=>>ZIO[Any,Throwable,X]] { 16 | val list0 = MyList.create(1,2,3,4,5) 17 | val list1 = list0.map(x => await(ZIO.succeed(x+1))) 18 | assert (list1 == MyList.create(2,3,4,5,6)) 19 | } 20 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(c)) 21 | } 22 | 23 | 24 | test("catch exception from failed operation inside runtime await") { 25 | val c=async[[X]=>>ZIO[Any,Throwable,X]] { 26 | val list0 = MyList.create(1,2,3,4,5) 27 | try { 28 | val list1 = list0.map[Int](x => await(ZIO.fail(new RuntimeException("test")))) 29 | assert (false) 30 | }catch{ 31 | case ex: RuntimeException => 32 | assert(ex.getMessage() == "test") 33 | } 34 | } 35 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(c)) 36 | } 37 | 38 | 39 | 40 | test("check apply function with await as argument to MyList.foldLeft") { 41 | val c=async[[X]=>>ZIO[Any,Throwable,X]] { 42 | val list0 = MyList.create(1,2,3,4,5) 43 | val sum = list0.foldLeft(0)((s,x) => await(ZIO.succeed(s+x))) 44 | assert (sum == 15) 45 | } 46 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(c)) 47 | } 48 | 49 | 50 | def twice[A](f: A=>A)(a:A):A = f(f(a)) 51 | 52 | test("check await in the argument of twice") { 53 | val c = async[[X]=>>ZIO[Any,Throwable,X]] { 54 | val r = twice[Int](x => await(incr(x)))(1) 55 | assert(r == 3) 56 | } 57 | c 58 | } 59 | 60 | 61 | test("check await in the argument of twice inside MyList.map") { 62 | val c = async[[X]=>>ZIO[Any,Throwable,X]] { 63 | val list0 = MyList.create(1,2,3,4,5) 64 | val list1 = list0.map(x => twice[Int](x => await(incr(x)))(x)) 65 | assert(list1 == MyList.create(3,4,5,6,7)) 66 | } 67 | c 68 | } 69 | 70 | 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /zio2-loom/src/test/scala/cps/zioloomtest/MyList.scala: -------------------------------------------------------------------------------- 1 | package cps.zioloomtest 2 | 3 | 4 | sealed trait MyList[+A] { 5 | 6 | def map[B](f: A=>B): MyList[B] 7 | 8 | def foldLeft[B](z: B)(f: (B, A) => B): B = this match { 9 | case MyNil => z 10 | case MyCons(hd, tl) => tl.foldLeft(f(z, hd))(f) 11 | } 12 | 13 | def length: Int 14 | 15 | } 16 | 17 | case object MyNil extends MyList[Nothing] { 18 | 19 | override def map[B](f: Nothing => B): MyList[B] = MyNil 20 | 21 | override def length: Int = 0 22 | 23 | } 24 | 25 | case class MyCons[T](hd: T, tl: MyList[T]) extends MyList[T] { 26 | 27 | override def map[B](f: T => B): MyList[B] = MyCons(f(hd), tl.map(f)) 28 | 29 | override def length: Int = 1 + tl.length 30 | 31 | } 32 | 33 | object MyList { 34 | 35 | def create[T](args: T*):MyList[T] = { 36 | if (args.isEmpty) MyNil 37 | else MyCons(args.head, create(args.tail*)) 38 | } 39 | 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /zio2/shared/src/main/scala/cps/monads/zio/ThrowableAdapter.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import zio._ 4 | 5 | 6 | case class ZIOErrorAdapter[E](e:E) extends RuntimeException 7 | 8 | object GenericThrowableAdapter: 9 | 10 | def toThrowable[E](e:E): Throwable = 11 | e match 12 | case eth: Throwable => eth 13 | case _ => new ZIOErrorAdapter(e) 14 | 15 | def fromThrowable[R,E,A](e:Throwable): ZIO[R,E,A] = 16 | e match 17 | case ZIOErrorAdapter(e1) => ZIO.fail(e1.asInstanceOf[E]) 18 | case other => ZIO.fail( other.asInstanceOf[E] ) 19 | 20 | 21 | /* 22 | // TODO: rething, mb change to static check 23 | given throwableForThrowable[R, X <: Throwable]: ThrowableAdapter[R, X] with 24 | def toThrowable(e: X): Throwable = e 25 | 26 | def fromThrowable[A](e: Throwable): ZIO[R, X, A] = 27 | ZIO.fail(e.asInstanceOf[X]) 28 | */ 29 | -------------------------------------------------------------------------------- /zio2/shared/src/main/scala/cps/monads/zio/Using.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import cps.* 4 | import zio.* 5 | import zio.managed.* 6 | 7 | /** 8 | * pseudo-synchronious variant of `use` for using inside async block. 9 | */ 10 | extension [R,E,A](r: ZManaged[R,E,A])(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]]) 11 | 12 | transparent inline def useOn[B](inline f: A=>B)(using CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 13 | await(r.use(a => m.pure(f(a)) )) 14 | 15 | /** 16 | * using ZManaged resource and close `r` after f will be finished. 17 | * Shpuld be used inside async block, 'f' can contains awaits. 18 | */ 19 | extension (zm: ZManaged.type) 20 | 21 | transparent inline def using[R,E,A,B](r:ZManaged[R,E,A])(inline f: A=>B)(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]], mc:CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 22 | await(r.use(a => m.pure(f(a)) )) 23 | 24 | transparent inline def using[R, E, A1, A2, B](r1:ZManaged[R,E,A1], r2:ZManaged[R,E,A2])(inline f: (A1,A2)=>B)(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]], mc:CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 25 | await(r1.use(a1 => r2.use(a2 => m.pure(f(a1,a2))))) 26 | 27 | transparent inline def using[R, E, A1, A2, A3, B](r1:ZManaged[R,E,A1], r2:ZManaged[R,E,A2], r3: ZManaged[R,E,A3])(inline f: (A1,A2,A3)=>B)(using m:CpsTryMonad[[X]=>>ZIO[R,E,X]], mc:CpsMonadContext[[X]=>>ZIO[R,E,X]]): B = 28 | await(r1.use(a1 => r2.use(a2 => r3.use(a3 => m.pure(f(a1,a2,a3)))))) 29 | 30 | -------------------------------------------------------------------------------- /zio2/shared/src/main/scala/cps/monads/zio/ZIOCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import cps._ 4 | import cps.macros._ 5 | import zio._ 6 | import scala.util._ 7 | import scala.concurrent._ 8 | 9 | abstract class CpsConcurentEffectMonadWithInstanceContext[F[_]] extends CpsConcurrentEffectMonad[F] with CpsMonadInstanceContext[F] 10 | 11 | class ZIOCpsMonad[R, E] extends CpsConcurentEffectMonadWithInstanceContext[[X]=>>ZIO[R,E,X]]: 12 | 13 | type F[T] = ZIO[R,E,T] 14 | 15 | type Spawned[T] = Fiber[E,T] 16 | 17 | def pure[A](x:A):ZIO[R,E,A] = ZIO.succeed(x) 18 | 19 | def map[A,B](fa: F[A])(f: A=>B): F[B] = 20 | fa.map(f) 21 | 22 | def flatMap[A,B](fa: F[A])(f: A=> F[B]): F[B] = 23 | fa.flatMap(f) 24 | 25 | def error[A](e: Throwable): F[A] = 26 | e match 27 | case ZIOErrorAdapter(e1) => ZIO.fail(e1.asInstanceOf[E]) 28 | case other => ZIO.fail(other.asInstanceOf[E]) 29 | 30 | def flatMapTry[A, B](fa: F[A])(f: util.Try[A] => F[B]): F[B] = 31 | fa.foldZIO( 32 | e => f(Failure(GenericThrowableAdapter.toThrowable(e))), 33 | a => f(Success(a)) 34 | ) 35 | 36 | def adoptCallbackStyle[A](source: (util.Try[A] => Unit) => Unit): F[A] = 37 | def adoptZIOCallback(zioCallback: ZIO[R,E,A]=>Unit): Try[A]=>Unit = { 38 | case Failure(ex) => zioCallback(error(ex)) 39 | case Success(a) => zioCallback(ZIO.succeed(a)) 40 | } 41 | ZIO.async[R,E,A] { cb => 42 | source(adoptZIOCallback(cb)) 43 | } 44 | 45 | def spawnEffect[A](op: =>F[A]):ZIO[R,E,Spawned[A]] = 46 | op.fork 47 | 48 | def join[A](op: Fiber[E,A]) = 49 | op.join 50 | 51 | def tryCancel[A](op: Fiber[E,A]):F[Unit] = 52 | op.interrupt.map(_ => ()) 53 | 54 | 55 | 56 | //object TaskCpsMonad extends ZIOCpsMonad[Any,Throwable] 57 | 58 | //given CpsConcurrentEffectMonad[Task] = TaskCpsMonad 59 | given zioCpsMonad[R,E]: ZIOCpsMonad[R,E] = ZIOCpsMonad[R,E]() 60 | 61 | //object ZIOCpsMonad: 62 | // given zioCpsMonad[R,E]: ZIOCpsMonad[R,E] = ZIOCpsMonad[R,E] 63 | 64 | transparent inline def asyncZIO[R,E](using ZIOCpsMonad[R,E]) = 65 | new cps.macros.Async.InferAsyncArg(using summon[ZIOCpsMonad[R,E]]) 66 | 67 | //transparent inline def asyncZIO[R,E]: Async.InferAsyncArg[[X]=>>ZIO[R,E,X],CpsMonadInstanceContext[[X]=>>ZIO[R,E,X]]] = 68 | // new cps.macros.Async.InferAsyncArg(using ZIOCpsMonad.zioCpsMonad) 69 | 70 | 71 | transparent inline def asyncRIO[R](using ZIOCpsMonad[R,Throwable]) = 72 | new Async.InferAsyncArg(using summon[ZIOCpsMonad[R,Throwable]]) 73 | 74 | 75 | given zioToZio[R1,R2<:R1,E1,E2>:E1]: CpsMonadConversion[[T] =>> ZIO[R1,E1,T], [T]=>>ZIO[R2,E2,T]] with 76 | 77 | def apply[T](ft:ZIO[R1,E1,T]): ZIO[R2,E2,T]= ft 78 | 79 | 80 | 81 | //given zioToRio[R]: CpsMonadConversion[[T] =>> ZIO[Nothing,Any,T], [T]=>>RIO[R,T]] with 82 | // 83 | // def apply[T](ft:ZIO[Nothing,Any,T]): RIO[R,T] = 84 | // val r1 = ft.foldZIO( 85 | // e => { 86 | // ZIO.fail[Throwable](GenericThrowableAdapter.toThrowable(e)) 87 | // }, 88 | // v => ZIO.succeed(v) 89 | // ) 90 | // val r2: RIO[R,T] = r1.asInstanceOf[ZIO[R,Throwable,T]] 91 | // r2 92 | 93 | 94 | 95 | given futureZIOConversion[R,E](using zio.Runtime[R]): 96 | CpsMonadConversion[[T]=>>ZIO[R,E,T],Future] with 97 | 98 | def apply[T](ft:ZIO[R,E,T]): Future[T] = 99 | Unsafe.unsafe( implicit unsafe => 100 | summon[Runtime[R]].unsafe.runToFuture(ft.mapError(e => GenericThrowableAdapter.toThrowable(e)))) 101 | 102 | 103 | 104 | given zioFutureConversion: CpsMonadConversion[Future,[T]=>>ZIO[Any,Throwable,T]] with 105 | 106 | def apply[T](ft:Future[T]): ZIO[Any,Throwable,T] = 107 | ZIO.fromFuture( ec => ft ) 108 | 109 | 110 | -------------------------------------------------------------------------------- /zio2/shared/src/main/scala/cps/monads/zio/ZManagedCpsMonad.scala: -------------------------------------------------------------------------------- 1 | package cps.monads.zio 2 | 3 | import cps._ 4 | import cps.macros._ 5 | import zio._ 6 | import zio.managed._ 7 | import scala.util._ 8 | import scala.concurrent._ 9 | 10 | trait CpsTryMonadWithInstanceContext[F[_]] extends CpsTryMonad[F] with CpsMonadInstanceContext[F] 11 | 12 | /** 13 | * CpsMonad which encapsulate effects with automatic resource management. 14 | * 15 | * Example of usage: 16 | * ``` 17 | * asyncRManaged[R] { 18 | * val input = FileChannel.open(inputPath) 19 | * val output = FileChannel.open(outputPath) 20 | * input.transformTo(0,Long.MaxValue,output) 21 | * } 22 | * ``` 23 | **/ 24 | class ZManagedCpsMonad[R, E] extends CpsTryMonadWithInstanceContext[[X]=>>ZManaged[R,E,X]]: 25 | 26 | type F[T] = ZManaged[R,E,T] 27 | 28 | def pure[A](x:A):ZManaged[R,E,A] = ZManaged.succeed(x) 29 | 30 | def map[A,B](fa: F[A])(f: A=>B): F[B] = 31 | fa.map(f) 32 | 33 | def flatMap[A,B](fa: F[A])(f: A=> F[B]): F[B] = 34 | fa.flatMap(f) 35 | 36 | def error[A](e: Throwable): F[A] = 37 | ZManaged.fromZIO(GenericThrowableAdapter.fromThrowable(e)) 38 | 39 | def flatMapTry[A, B](fa: F[A])(f: util.Try[A] => F[B]): F[B] = 40 | fa.foldManaged( 41 | e => f(Failure(GenericThrowableAdapter.toThrowable(e))), 42 | a => f(Success(a)) 43 | ) 44 | 45 | 46 | /* 47 | 48 | object TaskManagedCpsMonad extends ZManagedCpsMonad[Any,Throwable] 49 | 50 | given CpsTryMonad[TaskManaged] = TaskManagedCpsMonad 51 | */ 52 | 53 | given zManagedCpsMonad[R,E]: ZManagedCpsMonad[R,E] = ZManagedCpsMonad[R,E] 54 | 55 | transparent inline def asyncZManaged[R,E](using ZManagedCpsMonad[R,E]): Async.InferAsyncArg[[X]=>>ZManaged[R,E,X],CpsMonadInstanceContextBody[[X]=>>ZManaged[R,E,X]]] = 56 | new cps.macros.Async.InferAsyncArg 57 | 58 | transparent inline def asyncRManaged[R](using ZManagedCpsMonad[R,Throwable]): Async.InferAsyncArg[[X]=>>RManaged[R,X],CpsMonadInstanceContextBody[[X]=>>ZManaged[R,Throwable,X]]] = 59 | new Async.InferAsyncArg 60 | 61 | 62 | given zioToZManaged[R1,R2<:R1,E1,E2>:E1]: 63 | CpsMonadConversion[[T] =>> ZIO[R1,E1,T], 64 | [T]=>> ZManaged[R2,E2,T]] with 65 | 66 | def apply[T](ft:ZIO[R1,E1,T]): ZManaged[R2,E2,T]= 67 | ZManaged.fromZIO[R2,E2,T](ft) 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /zio2/shared/src/main/scala/cps/stream/zio/ZStreamEmitAbsorber.scala: -------------------------------------------------------------------------------- 1 | package cps.stream.zio 2 | 3 | import zio.* 4 | import zio.stream.* 5 | 6 | 7 | import cps.{*,given} 8 | import cps.monads.zio.{*,given} 9 | import cps.stream.{*,given} 10 | import scala.concurrent.* 11 | 12 | 13 | given ZStreamEmitAbsorber[R,E,O](using ExecutionContext): BaseUnfoldCpsAsyncEmitAbsorber[ZStream[R,E,O], [X]=>>ZIO[R,E,X], CpsMonadInstanceContextBody[[X]=>>ZIO[R,E,X]], O] with 14 | 15 | override type Element = O 16 | 17 | override def asSync(fs:ZIO[R,E,ZStream[R,E,O]]):ZStream[R,E,O] = 18 | ZStream.unwrap(fs) 19 | 20 | def unfold[S](s0:S)(f:S => ZIO[R,E,Option[(O,S)]]): ZStream[R,E,O] = 21 | ZStream.unfoldZIO[R,E,O,S](s0)(f) 22 | 23 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/BasicGeneratorSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import scala.concurrent.* 4 | 5 | import zio.* 6 | import zio.stream.* 7 | import cps.* 8 | import cps.monads.given 9 | import cps.monads.zio.{*,given} 10 | import cps.stream.zio.{*,given} 11 | 12 | 13 | import munit.* 14 | 15 | 16 | class BasicGeneratorSuite extends FunSuite { 17 | 18 | val N = 10000 19 | 20 | given ExecutionContext = ExecutionContext.global 21 | 22 | test("simple loop in ZStream") { 23 | 24 | val stream = asyncStream[Stream[Throwable,Int]] { out => 25 | for(i <- 1 to N) { 26 | out.emit(i) 27 | } 28 | } 29 | 30 | val res = stream.runFold(0)(_ + _) 31 | 32 | 33 | Unsafe.unsafe( implicit unsafe => 34 | Runtime.default.unsafe.runToFuture(res).map(x => 35 | assert(x == (1 to N).sum) 36 | ) ) 37 | 38 | } 39 | 40 | test("exception should break loop: ZStream") { 41 | val stream = asyncStream[Stream[Throwable, Int]] { out => 42 | for(i <- 1 to N) { 43 | if (i == N/2) then 44 | throw new RuntimeException("bye") 45 | out.emit(i) 46 | } 47 | } 48 | 49 | val res = stream.runFold(0)(_ + _) 50 | 51 | Unsafe.unsafe(implicit unsafe => 52 | Runtime.default.unsafe.runToFuture(res).failed.map(ex => assert(ex.getMessage()=="bye"))) 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/ConcurrentSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import zio._ 4 | import munit._ 5 | 6 | import scala.concurrent._ 7 | import scala.concurrent.duration._ 8 | 9 | import cps._ 10 | import cps.monads.given 11 | import cps.monads.zio.{given,*} 12 | import java.util.concurrent.atomic.AtomicInteger 13 | 14 | 15 | import scala.concurrent.ExecutionContext.Implicits.global 16 | 17 | class ConcurrentSuite extends munit.FunSuite { 18 | 19 | test("basic concurrent test") { 20 | 21 | val run = async[Task] { 22 | val m = summon[CpsConcurrentEffectMonad[Task]] 23 | val x = new AtomicInteger(0) 24 | val fiber1 = await(m.spawnEffect{ 25 | ZIO.attempt{ x.incrementAndGet() } 26 | }) 27 | val fiber2 = await(m.spawnEffect{ 28 | ZIO.attempt{ x.incrementAndGet() } 29 | }) 30 | val y1 = await(m.join(fiber1)) 31 | val y2 = await(m.join(fiber2)) 32 | assert(x.get() == 2 ) 33 | } 34 | 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/DCAIssue65Suite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import scala.concurrent.* 4 | import scala.concurrent.duration.* 5 | 6 | import zio.{Duration=>_,*} 7 | import zio.stream.* 8 | import cps.{async, asyncStream, await} 9 | import cps.stream.AsyncList 10 | import cps.monads.given 11 | import cps.monads.zio.{*,given} 12 | import cps.stream.zio.{*,given} 13 | 14 | 15 | import munit.* 16 | 17 | 18 | class DCAIssue65Suite extends FunSuite { 19 | 20 | val N = 100_000 21 | 22 | type ZF[X] = ZIO[Any, Throwable, X] 23 | 24 | override val munitTimeout = Duration(300, "s") 25 | 26 | given cps.macros.flags.PrintCode.type = cps.macros.flags.PrintCode 27 | 28 | def readingByIterator(ec: ExecutionContext)(implicit loc: munit.Location):Future[Long] = { 29 | given ExecutionContext = ec 30 | val stream: AsyncList[ZF, Int] = asyncStream[AsyncList[ZF, Int]] { out => 31 | out.emit(0) 32 | for i <- 1 to N do out.emit(i) 33 | } 34 | val ite = stream.iterator 35 | val compute: ZF[Long] = async[ZF] { 36 | var n = await(ite.next) 37 | var res: Long = 0 38 | while (n.nonEmpty) { 39 | res += n.get 40 | n = await(ite.next) 41 | } 42 | res 43 | } 44 | Unsafe.unsafe { implicit unsafe => zio.Runtime.default.unsafe.runToFuture(compute) } 45 | } 46 | 47 | test("dotty-cps-async:65:global:reading by iterator with global execution context") { 48 | readingByIterator(ExecutionContext.global) 49 | } 50 | 51 | test("dotty-cps-async:65:global:reading by iterator with parasitic execution context") { 52 | readingByIterator(ExecutionContext.parasitic) 53 | } 54 | 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/FutureInteropSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio._ 5 | import munit._ 6 | 7 | import scala.concurrent._ 8 | import scala.concurrent.duration._ 9 | 10 | import cps.* 11 | import cps.monads.{given,*} 12 | import cps.monads.zio.{given,*} 13 | 14 | import scala.concurrent.ExecutionContext.Implicits.global 15 | 16 | class FutureInteropSuite extends munit.FunSuite { 17 | 18 | class FutureBasedAPI { 19 | 20 | def getX: Future[Int] = Future successful 3 21 | 22 | } 23 | 24 | 25 | class ZIOBasedAPI { 26 | 27 | def getY: Task[Int] = ZIO.succeed(2) 28 | 29 | } 30 | 31 | 32 | test("make sure that ZIO async can adopt Future in Task") { 33 | val futureApi = new FutureBasedAPI() 34 | val zioApi = new ZIOBasedAPI() 35 | val run = async[Task] { 36 | val x = await(futureApi.getX) 37 | val y = await(zioApi.getY) 38 | assert(x + y == 5) 39 | } 40 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(run)) 41 | } 42 | 43 | test("make sure that ZIO async can adopt Future in RIO") { 44 | class Apis { 45 | val futureApi = new FutureBasedAPI() 46 | val zioApi = new ZIOBasedAPI() 47 | } 48 | val program = asyncRIO[Apis] { 49 | val apis = await(ZIO.environment[Apis]).get 50 | val futureApi = apis.futureApi 51 | val x = await(futureApi.getX) 52 | val y = await(ZIO.environmentWithZIO[Apis](_.get.zioApi.getY)) 53 | assert(x + y == 5) 54 | } 55 | val runtime = Runtime.default.mapEnvironment(_ => ZEnvironment[Apis](new Apis)) 56 | Unsafe.unsafe(implicit unsafe => runtime.unsafe.runToFuture(program)) 57 | } 58 | 59 | test("make sure that Future async can adopt ZIO with given Runtime") { 60 | val futureApi = new FutureBasedAPI() 61 | val zioApi = new ZIOBasedAPI() 62 | implicit val runtime = Runtime.default 63 | val futurePrg = async[Future] { 64 | val x = await(futureApi.getX) 65 | val y = await(zioApi.getY) 66 | assert(x + y == 5) 67 | } 68 | futurePrg 69 | } 70 | 71 | 72 | } 73 | 74 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/Issue4Example.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio.{given, *} 5 | import cps.monads.zio.{given, *} 6 | import cps.{given, *} 7 | 8 | object Issue4Example: 9 | private val ref = zio.FiberRef.make(Map.empty[String, Any]) 10 | 11 | def withCtx[R](body: => Task[R]) = async[[T]=>>ZIO[Scope,Throwable,T]] { 12 | val ctx = ref 13 | body 14 | } 15 | 16 | //def withCtxNoAwait[R](body: => Task[R]):Task[Unit] = { 17 | // 18 | // ref1.map(_ => ()) 19 | //} 20 | 21 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/LazyEffectSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio.* 5 | import munit.* 6 | import concurrent.duration.* 7 | 8 | import cps.* 9 | import cps.monads.zio.{given,*} 10 | 11 | 12 | class LazyEffectSuite extends FunSuite { 13 | 14 | import concurrent.ExecutionContext.Implicits.global 15 | 16 | test("zio: make sure that evaluation of async expression is delayed") { 17 | 18 | implicit val printCode = cps.macros.flags.PrintCode 19 | var x = 0 20 | val c = async[Task] { 21 | x = 1 22 | } 23 | assert(x == 0) 24 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(c).map{ r => 25 | assert(x == 1 ) 26 | }) 27 | 28 | } 29 | 30 | test("zio: make sure that exception is catched inside async expression ") { 31 | //implicit val printCode = cps.macroFlags.PrintCode 32 | val c1 = async[Task] { 33 | throw new RuntimeException("AAA") 34 | } 35 | val c2 = async[Task] { 36 | var x = 0 37 | var y = 0 38 | try { 39 | await(c1) 40 | x = 1 41 | }catch{ 42 | case ex: RuntimeException => 43 | assert(ex.getMessage()=="AAA") 44 | y = 2 45 | } 46 | assert(x == 0) 47 | assert(y == 2) 48 | } 49 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(c2)) 50 | } 51 | 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/ResourceSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio.* 5 | import munit.* 6 | import scala.concurrent.duration.* 7 | import scala.collection.mutable.ArrayBuffer 8 | 9 | import cps.* 10 | import cps.monads.zio.{given,*} 11 | import zio.managed.* 12 | 13 | 14 | class WriterEmu { 15 | 16 | var isClosed: Boolean = false 17 | val messages: ArrayBuffer[String] = ArrayBuffer.empty 18 | 19 | def write(message:String): Unit = { 20 | if (!isClosed) 21 | messages.append(message) 22 | else 23 | throw new IllegalStateException("resource is already closed") 24 | } 25 | 26 | def close(): Unit = 27 | isClosed = true 28 | 29 | } 30 | 31 | 32 | 33 | class ResourceSuite extends FunSuite { 34 | 35 | import concurrent.ExecutionContext.Implicits.global 36 | 37 | 38 | def makeWriterEmu(): TaskManaged[WriterEmu] = 39 | ZManaged.acquireReleaseInterruptibleWith(ZIO.attempt(new WriterEmu()))(a => ZIO.succeed(a.close())) 40 | 41 | test("using ZManaged") { 42 | //implicit val printCode = cps.macros.flags.PrintCode 43 | val prg = async[Task] { 44 | val r = makeWriterEmu() 45 | val ctr = await(Ref.make(0)) 46 | ZManaged.using(r) { a => 47 | val c0 = await(ctr.get) 48 | a.write(s"AAA-${c0}") 49 | await(ctr.update(_ + 1)) 50 | val c1 = await(ctr.get) 51 | a.write(s"BBB-${c1}") 52 | (a, a.messages.toList, a.isClosed) 53 | } 54 | } 55 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(prg).map{ 56 | (a, messages, isClosedInside) => 57 | //println(s"a = $a") 58 | //println(s"messages = $messages") 59 | assert(a.isClosed) 60 | assert(!isClosedInside) 61 | }) 62 | 63 | } 64 | 65 | } 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/StupidFizzBuzzSuite.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | 4 | import zio.* 5 | import munit.* 6 | import concurrent.duration.* 7 | 8 | import cps.* 9 | import cps.monads.zio.{given,*} 10 | 11 | 12 | class StupidFizzBuzzSuite extends FunSuite { 13 | 14 | import concurrent.ExecutionContext.Implicits.global 15 | 16 | test("make sure that FizBuzz run N times in async loop") { 17 | 18 | val program = asyncRIO[TLogging.Service] { 19 | val ctr = await(Ref.make(0)) 20 | while { 21 | val v = await(ctr.get) 22 | await(TLog.logMsg(v.toString)) 23 | if v % 3 == 0 then 24 | await(TLog.logMsg("fizz")) 25 | if v % 5 == 0 then 26 | await(TLog.logMsg("buzz")) 27 | await(ctr.update(_ + 1)) 28 | v < 10 29 | } do () 30 | await(TLog.lastRecords(20)) 31 | } 32 | 33 | val logService: TLogging.Service = new TLoggingImpl.Service 34 | val r = program.provideLayer( ZLayer.succeed(logService) ) 35 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(r).map{ logs => 36 | //println(s"logs=$logs") 37 | assert(logs(0)==TLogging.MsgRecord("0")) 38 | assert(logs(1)==TLogging.MsgRecord("fizz")) 39 | assert(logs(2)==TLogging.MsgRecord("buzz")) 40 | assert(logs(3)==TLogging.MsgRecord("1")) 41 | assert(logs(4)==TLogging.MsgRecord("2")) 42 | assert(logs(5)==TLogging.MsgRecord("3")) 43 | assert(logs(6)==TLogging.MsgRecord("fizz")) 44 | assert(logs(7)==TLogging.MsgRecord("4")) 45 | } 46 | ) 47 | } 48 | 49 | 50 | test("minimal [automatic coloring removed]") { 51 | import cps.syntax.{*,given} 52 | import scala.language.postfixOps 53 | //implicit val printCode = cps.macroFlags.PrintCode 54 | 55 | val program = asyncRIO[TLogging.Service] { 56 | val ctr = cps.syntax.unary_!( Ref.make(0) ) 57 | val v = await(ctr.get) 58 | cps.syntax.unary_!(TLog.logMsg("AAA")) 59 | val records: IndexedSeq[TLogging.LogRecord] = await(TLog.lastRecords(20)) 60 | records 61 | } 62 | val logService: TLogging.Service = new TLoggingImpl.Service 63 | val r = program.provideLayer( ZLayer.succeed(logService) ) 64 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(r)) 65 | } 66 | 67 | 68 | test("make sure that FizBuzz run N times in async loop [ automatic coloring removed]") { 69 | 70 | //implicit val printCode = cps.macroFlags.PrintCode 71 | //implicit val printTree = cps.macroFlags.PrintTree 72 | //implicit val debugLevel = cps.macroFlags.DebugLevel(20) 73 | 74 | val program = asyncRIO[TLogging.Service] { 75 | // TODO: find issue, while ctr.get search for option. 76 | // Now, let's do type ascription to force await. 77 | val ctr: Ref[Int] = await(Ref.make(0)) 78 | while { 79 | val v = await(ctr.get) 80 | await(TLog.logMsg(v.toString)) 81 | if v % 3 == 0 then 82 | await(TLog.logMsg("fizz")) 83 | if v % 5 == 0 then 84 | await(TLog.logMsg("buzz")) 85 | await(ctr.update(_ + 1)) 86 | v < 10 87 | } do () 88 | await(TLog.lastRecords(20)) 89 | } 90 | 91 | val logService: TLogging.Service = new TLoggingImpl.Service 92 | val r = program.provideLayer( ZLayer.succeed(logService) ) 93 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(r).map{ logs => 94 | //println(s"logs=$logs") 95 | assert(logs(0)==TLogging.MsgRecord("0")) 96 | assert(logs(1)==TLogging.MsgRecord("fizz")) 97 | assert(logs(6)==TLogging.MsgRecord("fizz")) 98 | assert(logs(7)==TLogging.MsgRecord("4")) 99 | }) 100 | 101 | } 102 | 103 | 104 | } 105 | 106 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/TLogging.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import zio._ 4 | 5 | 6 | object TLogging { 7 | 8 | sealed class LogRecord 9 | case class OpRecord(op: String) extends LogRecord 10 | case class MsgRecord(msg: String) extends LogRecord 11 | case class ExceptionRecord(ex: Throwable) extends LogRecord 12 | 13 | trait Service { 14 | def lastOp(): Task[Option[String]] 15 | def logOp(op: String): UIO[Unit] 16 | def logMsg(msg: String): UIO[Unit] 17 | def logThrowable(ex: Throwable): Task[Unit] 18 | 19 | def lastRecords(n: Int): Task[IndexedSeq[LogRecord]] 20 | } 21 | 22 | } 23 | 24 | 25 | object TLog { 26 | 27 | def lastOp(): RIO[TLogging.Service, Option[String]] = 28 | ZIO.serviceWithZIO[TLogging.Service](_.lastOp()) 29 | 30 | def logOp(op:String): RIO[TLogging.Service, Unit] = 31 | ZIO.serviceWithZIO(_.logOp(op)) 32 | 33 | def logMsg(msg:String): RIO[TLogging.Service, Unit] = 34 | ZIO.serviceWithZIO(_.logMsg(msg)) 35 | 36 | def logThrowable(ex: Throwable): RIO[TLogging.Service, Unit] = 37 | ZIO.serviceWithZIO(_.logThrowable(ex)) 38 | 39 | def lastRecords(n: Int): RIO[TLogging.Service, IndexedSeq[TLogging.LogRecord]] = 40 | ZIO.serviceWithZIO(_.lastRecords(n)) 41 | 42 | } 43 | 44 | 45 | object TLoggingImpl { 46 | 47 | import TLogging._ 48 | 49 | val layer: URLayer[Any,TLogging.Service] = ZLayer.fromFunction(()=>Service()) 50 | 51 | 52 | class Service extends TLogging.Service { 53 | 54 | // will not use multithreading in tests. 55 | var lastOpCache: Option[String] = None 56 | var log: IndexedSeq[LogRecord] = IndexedSeq() 57 | 58 | def lastOp(): Task[Option[String]] = ZIO.attempt(lastOpCache) 59 | 60 | def logOp(op: String): UIO[Unit] = 61 | ZIO.succeed{ 62 | lastOpCache = Some(op) 63 | log = log :+ OpRecord(op) 64 | } 65 | 66 | def logMsg(msg: String): UIO[Unit] = 67 | ZIO.succeed{ 68 | log = log :+ MsgRecord(msg) 69 | } 70 | 71 | def logThrowable(ex: Throwable): Task[Unit] = 72 | ZIO.attempt{ 73 | log = log :+ ExceptionRecord(ex) 74 | } 75 | 76 | def lastRecords(n: Int): Task[IndexedSeq[LogRecord]] = 77 | ZIO.attempt{ log.takeRight(n) } 78 | 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /zio2/shared/src/test/scala/cpszio/TestAsyncZIO.scala: -------------------------------------------------------------------------------- 1 | package cpszio 2 | 3 | import cps.* 4 | import cps.monads.zio.{given,*} 5 | 6 | import zio._ 7 | 8 | import munit.* 9 | 10 | 11 | 12 | 13 | class TestAsyncZIO extends munit.FunSuite { 14 | 15 | 16 | test("simple test of asyncZIO-1") { 17 | import scala.concurrent.ExecutionContext.Implicits.global 18 | val program = asyncZIO[TLogging.Service, Throwable] { 19 | val intRef = await(Ref.make(0)) 20 | await(TLog.logOp("createRef")) 21 | val date = await(Clock.currentDateTime) 22 | await(TLog.logOp("getDate")) 23 | await(TLog.lastRecords(10)) 24 | } 25 | val logService: TLogging.Service = new TLoggingImpl.Service 26 | val r = program.provideLayer(ZLayer.succeed(logService)) 27 | Unsafe.unsafe(implicit unsafe => Runtime.default.unsafe.runToFuture(r) 28 | .map{ logs => 29 | //println(s"logs=$logs") 30 | assert(logs(0)==TLogging.OpRecord("createRef")) 31 | assert(logs(1)==TLogging.OpRecord("getDate")) 32 | }) 33 | } 34 | 35 | } 36 | 37 | 38 | --------------------------------------------------------------------------------