├── .gitignore ├── .java-version ├── .jvmopts ├── .travis.yml ├── LICENSE ├── NOTICE ├── README.md ├── build.sbt ├── cats └── src │ ├── main │ └── scala │ │ └── scalaz │ │ └── ioeffect │ │ └── catz.scala │ └── test │ └── scala │ └── scalaz │ └── ioeffect │ ├── IOCatsLawsTest.scala │ └── IOScalaCheckInstances.scala ├── ioeffect └── src │ ├── main │ └── scala │ │ └── scalaz │ │ └── ioeffect │ │ ├── Async.scala │ │ ├── Errors.scala │ │ ├── ExitResult.scala │ │ ├── Fiber.scala │ │ ├── IO.scala │ │ ├── IOInstances.scala │ │ ├── IORef.scala │ │ ├── MonadIO.scala │ │ ├── Promise.scala │ │ ├── RTS.scala │ │ ├── SafeApp.scala │ │ ├── Void.scala │ │ ├── console │ │ └── package.scala │ │ └── package.scala │ └── test │ └── scala │ └── scalaz │ └── ioeffect │ ├── IOTest.scala │ └── RTSTest.scala └── project ├── ProjectPlugin.scala ├── build.properties ├── plugins.sbt ├── project └── plugins.sbt ├── scalafix.conf └── scalafmt.conf /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .sbtopts 3 | project/.sbt 4 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /.jvmopts: -------------------------------------------------------------------------------- 1 | -Dfile.encoding=UTF8 2 | -XX:MaxMetaspaceSize=1g 3 | -Xss2m 4 | -Xms1g 5 | -Xmx1g 6 | -XX:ReservedCodeCacheSize=128m 7 | -XX:+CMSClassUnloadingEnabled 8 | -XX:+UseG1GC 9 | -XX:+UseStringDeduplication 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | jdk: 3 | - openjdk8 4 | 5 | script: 6 | - sbt ";+check ;+cpl ;+test" 7 | - if [ -z "$TRAVIS_PULL_REQUEST" ] && [ "${TRAVIS_REPO_SLUG}" == "scalaz/ioeffect" ] ; then sbt publish ; fi 8 | 9 | before_cache: 10 | - find $HOME/.sbt -name "*.lock" | xargs rm 11 | - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm 12 | 13 | cache: 14 | directories: 15 | - $HOME/.sbt 16 | - $HOME/.ivy2 17 | - $HOME/.coursier 18 | 19 | notifications: 20 | email: false 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2018 Tony Morris, Runar Bjarnason, Tom Adams, Kristian Domagala, Brad Clow, Ricky Clarkson, Paul Chiusano, Trygve Laugstøl, Nick Partridge, Jason Zaugg, Sam Halliday, John De Goes 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the name of the copyright holder nor the names of 13 | its contributors may be used to endorse or promote products derived from 14 | this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | scalaz-ioeffect 2 | 3 | See the LICENSE file for more details. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/scalaz/ioeffect.svg?branch=master)](https://travis-ci.org/scalaz/ioeffect) 2 | 3 | Backport of the [IO Monad](http://degoes.net/articles/only-one-io) to scalaz 7.2. 4 | 5 | There are no binary or source compatibility guarantees between releases of this preview. 6 | 7 | ```scala 8 | libraryDependencies += "org.scalaz" %% "scalaz-ioeffect" % "" 9 | ``` 10 | 11 | where `` is the latest on [maven central](http://search.maven.org/#search|ga|1|g:org.scalaz%20a:scalaz-ioeffect_2.12). 12 | 13 | # scalaz-ioeffect 14 | 15 | The `scalaz.ioeffect` package provides a general-purpose effect monad and associated abstractions for purely functional Scala applications. 16 | 17 | The package strives to deliver on the following design goals: 18 | 19 | - **Principled**. A purely functional interface for effectful code with rigorous, well-defined semantics. 20 | - **Performant**. A low-level, highly-optimized runtime system that offers performance better than or comparable to other effect monads. 21 | - **Pragmatic**. The composable, orthogonal primitives necessary to build real world software, including primitives for concurrent and asynchronous programming. 22 | 23 | # Why IO? 24 | 25 | Effect monads like `IO` are how purely functional programs interact with the real world. Functional programmers use them to build complex, real world software without giving up the equational reasoning, composability, and type safety afforded by purely functional programming. 26 | 27 | However, there are many practical reasons to build your programs using `IO`, including all of the following: 28 | 29 | * **Asynchronicity**. Like Scala's own `Future`, `IO` lets you easily write asynchronous code without blocking or callbacks. Compared to `Future`, `IO` has significantly better performance and cleaner, more expressive, and more composable semantics. 30 | * **Composability**. Purely functional code can't be combined with impure code that has side-effects without the straightforward reasoning properties of functional programming. `IO` lets you wrap up all effects into a purely functional package that lets you build composable real world programs. 31 | * **Concurrency**. `IO` has all the concurrency features of `Future`, and more, based on a clean fiber concurrency model designed to scale well past the limits of native threads. Unlike other effect monads, `IO`'s concurrency primitives do not leak threads. 32 | * **Interruptibility**. All concurrent computations can be interrupted, in a way that still guarantees resources are cleaned up safely, allowing you to write aggressively parallel code that doesn't waste valuable resources or bring down production servers. 33 | * **Resource Safety**. `IO` provides composable resource-safe primitives that ensure resources like threads, sockets, and file handles are not leaked, which allows you to build long-running, robust applications. These applications will not leak resources, even in the presence of errors or interruption. 34 | * **Immutability**. `IO`, like Scala's immutable collection types, is an immutable data structure. All `IO` methods and functions return new `IO` values. This lets you reason about `IO` values the same way you reason about immutable collections. 35 | * **Reification**. `IO` reifies programs. In non-functional Scala programming, you cannot pass programs around or store them in data structures, because programs are not values. But `IO` turns your programs into ordinary values, and lets you pass them around and compose them with ease. 36 | * **Performance**. Although simple, synchronous `IO` programs tend to be slower than the equivalent imperative Scala, `IO` is extremely fast given all the expressive features and strong guarantees it provides. Ordinary imperative Scala could not match this level of expressivity and performance without tedious, error-prone boilerplate that no one would write in real-life. 37 | 38 | While functional programmers *must* use `IO` (or something like it) to represent effects, nearly all programmers will find the features of `IO` help them build scalable, performant, concurrent, and leak-free applications faster and with stronger correctness guarantees than legacy techniques allow. 39 | 40 | Use `IO` because it's simply not practical to write real-world, correct software without it. 41 | 42 | # Introduction 43 | 44 | A value of type `IO[E, A]` describes an effect that may fail with an `E`, run forever, or produce a single `A`. 45 | 46 | `IO` values are immutable, and all `IO` functions produce new `IO` values, enabling `IO` to be reasoned about and used like any ordinary Scala immutable data structure. 47 | 48 | `IO` values do not actually _do_ anything. However, they may be interpreted by the `IO` runtime system into effectful interactions with the external world. Ideally, this occurs at a single time, in your application's `main` function (`SafeApp` provides this functionality automatically). 49 | 50 | # Usage 51 | 52 | ## Main 53 | 54 | Your main function can extend `SafeApp`, which provides a complete runtime 55 | system and allows your entire program to be purely functional. 56 | 57 | ```scala 58 | import scalaz.ioeffect.{IO, SafeApp} 59 | import scalaz.ioeffect.console._ 60 | 61 | import java.io.IOException 62 | 63 | object MyApp extends SafeApp { 64 | 65 | def run(args: List[String]): IO[Void, ExitStatus] = 66 | myAppLogic.attempt.map(_.fold(_ => 1, _ => 0)).map(ExitStatus.ExitNow(_)) 67 | 68 | def myAppLogic: IO[IOException, Unit] = 69 | for { 70 | _ <- putStrLn("Hello! What is your name?") 71 | n <- getStrLn 72 | _ <- putStrLn("Hello, " + n + ", good to meet you!") 73 | } yield () 74 | } 75 | ``` 76 | 77 | ## Pure Values 78 | 79 | You can lift pure values into `IO` with `IO.point`: 80 | 81 | ```scala 82 | val liftedString: IO[Void, String] = IO.point("Hello World") 83 | ``` 84 | 85 | The constructor uses non-strict evaluation, so the parameter will not be evaluated until when and if the `IO` action is executed at runtime. 86 | 87 | Alternately, you can use the `IO.now` constructor for strict evaluation: 88 | 89 | ```scala 90 | val lifted: IO[Void, String] = IO.now("Hello World") 91 | ``` 92 | 93 | You should never use either constructor for importing impure code into `IO`. The result of doing so is undefined. 94 | 95 | ## Impure Code 96 | 97 | You can use the `sync` method of `IO` to import effectful synchronous code into your purely functional program: 98 | 99 | ```scala 100 | val nanoTime: IO[Void, Long] = IO.sync(System.nanoTime()) 101 | ``` 102 | 103 | If you are importing effectful code that may throw exceptions, you can use the `syncException` method of `IO`: 104 | 105 | ```scala 106 | def readFile(name: String): IO[Exception, ByteArray] = 107 | IO.syncException(FileUtils.readBytes(name)) 108 | ``` 109 | 110 | The `syncCatch` method is more general, allowing you to catch and optionally translate any type of `Throwable` into an error type. 111 | 112 | You can use the `async` method of `IO` to import effectful asynchronous code into your purely functional program: 113 | 114 | ```scala 115 | def makeRequest(req: Request): IO[HttpException, Response] = 116 | IO.async(cb => Http.req(req, cb)) 117 | ``` 118 | 119 | ## Mapping 120 | 121 | You can change an `IO[E, A]` to an `IO[E, B]` by calling the `map` method with a function `A => B`. This lets you transform values produced by actions into other values. 122 | 123 | ```scala 124 | val answer = IO.point(21).map(_ * 2) 125 | ``` 126 | 127 | You can transform an `IO[E, A]` into an `IO[E2, A]` by calling the `leftMap` method with a function `E => E2`: 128 | 129 | ```scala 130 | val response: IO[AppError, Response] = 131 | makeRequest(r).leftMap(AppError(_)) 132 | ``` 133 | 134 | ## Chaining 135 | 136 | You can execute two actions in sequence with the `flatMap` method. The second action may depend on the value produced by the first action. 137 | 138 | ```scala 139 | val contacts: IO[IOException, IList[Contact]] = 140 | readFile("contacts.csv").flatMap((file: ByteArray) => 141 | parseCsv(file).map((csv: IList[CsvRow]) => 142 | csv.map(rowToContact) 143 | ) 144 | ) 145 | ``` 146 | 147 | You can use Scala's `for` comprehension syntax to make this type of code more compact: 148 | 149 | ```scala 150 | val contacts: IO[IOException, IList[Contact]] = 151 | for { 152 | file <- readFile("contacts.csv") 153 | csv <- parseCsv(file) 154 | } yield csv.map(rowToContact) 155 | ``` 156 | 157 | ## Failure 158 | 159 | You can create `IO` actions that describe failure with `IO.fail`: 160 | 161 | ```scala 162 | val failure: IO[String, Unit] = IO.fail("Oh noes!") 163 | ``` 164 | 165 | Like all `IO` values, these are immutable values and do not actually throw any exceptions; they merely describe failure as a first-class value. 166 | 167 | You can surface failures with `attempt`, which takes an `IO[E, A]` and produces an `IO[E2, E \/ A]`. The choice of `E2` is unconstrained, because the resulting computation cannot fail with any error. 168 | 169 | The `scalaz.Void` type makes a suitable choice to describe computations that cannot fail: 170 | 171 | ```scala 172 | val file: IO[Void, Data] = readData("data.json").attempt[Void].map { 173 | case -\/ (_) => IO.point(NoData) 174 | case \/-(data) => IO.point(data) 175 | } 176 | ``` 177 | 178 | You can submerge failures with `IO.absolve`, which is the opposite of `attempt` and turns an `IO[E, E \/ A]` into an `IO[E, A]`: 179 | 180 | ```scala 181 | def sqrt(io: IO[Void, Double]): IO[NonNegError, Double] = 182 | IO.absolve( 183 | io[NonNegError].map(value => 184 | if (value < 0.0) -\/(NonNegError) 185 | else \/-(Math.sqrt(value)) 186 | ) 187 | ) 188 | ``` 189 | 190 | If you want to catch and recover from all types of errors and effectfully attempt recovery, you can use the `catchAll` method: 191 | 192 | ```scala 193 | openFile("primary.json").catchAll(_ => openFile("backup.json")) 194 | ``` 195 | 196 | If you want to catch and recover from only some types of exceptions and effectfully attempt recovery, you can use the `catchSome` method: 197 | 198 | ```scala 199 | openFile("primary.json").catchSome { 200 | case FileNotFoundException(_) => openFile("backup.json") 201 | } 202 | ``` 203 | 204 | You can execute one action, or, if it fails, execute another action, with the `orElse` combinator: 205 | 206 | ```scala 207 | val file = openFile("primary.json").orElse(openFile("backup.json")) 208 | ``` 209 | 210 | ### Retry 211 | 212 | There are a number of useful combinators for repeating actions until failure or success: 213 | 214 | * `IO.forever` — Repeats the action until the first failure. 215 | * `IO.retry` — Repeats the action until the first success. 216 | * `IO.retryN(n)` — Repeats the action until the first success, for up to the specified number of times (`n`). 217 | * `IO.retryFor(d)` — Repeats the action until the first success, for up to the specified amount of time (`d`). 218 | 219 | ## Brackets 220 | 221 | Brackets are a built-in primitive that let you safely acquire and release resources. 222 | 223 | Brackets are used for a similar purpose as try/catch/finally, only brackets work with synchronous and asynchronous actions, work seamlessly with fiber interruption, and are built on a different error model that ensures no errors are ever swallowed. 224 | 225 | Brackets consist of an *acquire* action, a *utilize* action (which uses the acquired resource), and a *release* action. 226 | 227 | The release action is guaranteed to be executed by the runtime system, even if the utilize action throws an exception or the executing fiber is interrupted. 228 | 229 | ```scala 230 | openFile("data.json").bracket(closeFile(_)) { file => 231 | for { 232 | data <- decodeData(file) 233 | grouped <- groupData(data) 234 | } yield grouped 235 | } 236 | ``` 237 | 238 | Brackets have compositional semantics, so if a bracket is nested inside another bracket, and the outer bracket acquires a resource, then the outer bracket's release will always be called, even if, for example, the inner bracket's release fails. 239 | 240 | A helper method called `ensuring` provides a simpler analogue of `finally`: 241 | 242 | ```scala 243 | val composite = action1.ensuring(cleanupAction) 244 | ``` 245 | 246 | ## Fibers 247 | 248 | To perform an action without blocking the current process, you can use fibers, which are a lightweight mechanism for concurrency. 249 | 250 | You can `fork` any `IO[E, A]` to immediately yield an `IO[Void, Fiber[E, A]]`. The provided `Fiber` can be used to `join` the fiber, which will resume on production of the fiber's value, or to `interrupt` the fiber with some exception. 251 | 252 | ```scala 253 | val analyzed = 254 | for { 255 | fiber1 <- analyzeData(data).fork // IO[E, Analysis] 256 | fiber2 <- validateData(data).fork // IO[E, Boolean] 257 | ... // Do other stuff 258 | valid <- fiber2.join 259 | _ <- if (!valid) fiber1.interrupt(DataValidationError(data)) 260 | else IO.unit 261 | analyzed <- fiber1.join 262 | } yield analyzed 263 | ``` 264 | 265 | On the JVM, fibers will use threads, but will not consume *unlimited* threads. Instead, fibers yield cooperatively during periods of high-contention. 266 | 267 | ```scala 268 | def fib(n: Int): IO[Void, Int] = 269 | if (n <= 1) IO.point(1) 270 | else for { 271 | fiber1 <- fib(n - 2).fork 272 | fiber2 <- fib(n - 1).fork 273 | v2 <- fiber2.join 274 | v1 <- fiber1.join 275 | } yield v1 + v2 276 | ``` 277 | 278 | Interrupting a fiber returns an action that resumes when the fiber has completed or has been interrupted and all its finalizers have been run. These precise semantics allow construction of programs that do not leak resources. 279 | 280 | A more powerful variant of `fork`, called `fork0`, allows specification of supervisor that will be passed any non-recoverable errors from the forked fiber, including all such errors that occur in finalizers. If this supervisor is not specified, then the supervisor of the parent fiber will be used, recursively, up to the root handler, which can be specified in `RTS` (the default supervisor merely prints the stack trace). 281 | 282 | ## Error Model 283 | 284 | The `IO` error model is simple, consistent, permits both typed errors and termination, and does not violate any laws in the `Functor` hierarchy. 285 | 286 | An `IO[E, A]` value may only raise errors of type `E`. These errors are recoverable, and may be caught the `attempt` method. The `attempt` method yields a value that cannot possibly fail with any error `E`. This rigorous guarantee can be reflected at compile-time by choosing a new error type such as `Nothing` or `Void`, which is possible because `attempt` is polymorphic in the error type of the returned value. 287 | 288 | Separately from errors of type `E`, a fiber may be terminated for the following reasons: 289 | 290 | * The fiber self-terminated or was interrupted by another fiber. The "main" fiber cannot be interrupted because it was not forked from any other fiber. 291 | * The fiber failed to handle some error of type `E`. This can happen only when an `IO.fail` is not handled. For values of type `IO[Void, A]`, this type of failure is impossible. 292 | * The fiber has a defect that leads to a non-recoverable error. There are only two ways this can happen: 293 | 1. A partial function is passed to a higher-order function such as `map` or `flatMap`. For example, `io.map(_ => throw e)`, or `io.flatMap(a => throw e)`. The solution to this problem is to not to pass impure functions to purely functional libraries like Scalaz, because doing so leads to violations of laws and destruction of equational reasoning. 294 | 2. Error-throwing code was embedded into some value via `IO.point`, `IO.sync`, etc. For importing partial effects into `IO`, the proper solution is to use a method such as `syncException`, which safely translates exceptions into values. 295 | 296 | When a fiber is terminated, the reason for the termination, expressed as a `Throwable`, is passed to the fiber's supervisor, which may choose to log, print the stack trace, restart the fiber, or perform some other action appropriate to the context. 297 | 298 | A fiber cannot stop its own termination. However, all finalizers will be run during termination, even when some finalizers throw non-recoverable errors. Errors thrown by finalizers are passed to the fiber's supervisor. 299 | 300 | There are no circumstances in which any errors will be "lost", which makes the `IO` error model more diagnostic-friendly than the `try`/`catch`/`finally` construct that is baked into both Scala and Java, which can easily lose errors. 301 | 302 | ### Parallelism 303 | 304 | To execute actions in parallel, the `par` method can be used: 305 | 306 | ```scala 307 | def bigCompute(m1: Matrix, m2: Matrix, v: Matrix): IO[Void, Matrix] = 308 | for { 309 | t <- computeInverse(m1).par(computeInverse(m2)) 310 | val (i1, i2) = t 311 | r <- applyMatrices(i1, i2, v) 312 | } yield r 313 | ``` 314 | 315 | The `par` combinator has resource-safe semantics. If one computation fails, the other computation will be interrupted, to prevent wasting resources. 316 | 317 | ### Racing 318 | 319 | Two `IO` actions can be *raced*, which means they will be executed in parallel, and the value of the first action that completes successfully will be returned. 320 | 321 | ```scala 322 | action1.race(action2) 323 | ``` 324 | 325 | The `race` combinator is resource-safe, which means that if one of the two actions returns a value, the other one will be interrupted, to prevent wasting resources. 326 | 327 | The `race` and even `par` combinators are a specialization of a much-more powerful combinator called `raceWith`, which allows executing user-defined logic when the first of two actions succeeds. 328 | 329 | # Performance 330 | 331 | `scalaz.ioeffect` has excellent performance, featuring a hand-optimized, low-level interpreter that achieves zero allocations for right-associated binds, and minimal allocations for left-associated binds. 332 | 333 | The `benchmarks` project may be used to compare `IO` with other effect monads, including `Future` (which is not an effect monad but is included for reference), Monix `Task`, and Cats `IO`. 334 | 335 | As of the time of this writing, `IO` is significantly faster than or at least comparable to all other purely functional solutions. 336 | 337 | # Stack Safety 338 | 339 | `IO` is stack-safe on infinitely recursive `flatMap` invocations, for pure values, synchronous effects, and asynchronous effects. `IO` is guaranteed to be stack-safe on repeated `map` invocations (`io.map(f1).map(f2)...map(f10000)`), for at least 10,000 repetitions. 340 | 341 | | | map | flatMap | 342 | |-----:|:-----------------:|:---------:| 343 | | sync | 10,000 iterations | unlimited | 344 | | async| unlimited | unlimited | 345 | 346 | # Thread Shifting - JVM 347 | 348 | By default, fibers make no guarantees as to which thread they execute on. They may shift between threads, especially as they execute for long periods of time. 349 | 350 | Fibers only ever shift onto the thread pool of the runtime system, which means that by default, fibers running for a sufficiently long time will always return to the runtime system's thread pool, even when their (asynchronous) resumptions were initiated from other threads. 351 | 352 | For performance reasons, fibers will attempt to execute on the same thread for a (configurable) minimum period, before yielding to other fibers. Fibers that resume from asynchronous callbacks will resume on the initiating thread, and continue for some time before yielding and resuming on the runtime thread pool. 353 | 354 | These defaults help guarantee stack safety and cooperative multitasking. They can be changed in `RTS` if automatic thread shifting is not desired. 355 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val ioeffect = project.settings( 2 | name := "scalaz-ioeffect" 3 | ) 4 | 5 | val cats = project 6 | .settings( 7 | name := "scalaz-ioeffect-cats", 8 | libraryDependencies ++= Seq( 9 | "org.typelevel" %% "cats-effect" % "0.10.1", 10 | "org.typelevel" %% "cats-effect-laws" % "0.10.1" % "test", 11 | "org.typelevel" %% "cats-testkit" % "1.1.0" % "test", 12 | "com.github.alexarchambault" %% "scalacheck-shapeless_1.13" % "1.1.8" % "test" 13 | ) 14 | ) 15 | .dependsOn(ioeffect) 16 | 17 | // conveniences 18 | addCommandAlias("cpl", "all compile test:compile") 19 | addCommandAlias("fmt", "all scalafmtSbt scalafmt test:scalafmt") 20 | addCommandAlias("check", "all scalafmtSbtCheck scalafmtCheck test:scalafmtCheck") 21 | addCommandAlias("lint", "all compile:scalafixTest test:scalafixTest") 22 | addCommandAlias("fix", "all compile:scalafixCli test:scalafixCli") 23 | 24 | // root project 25 | skip in publish := true 26 | -------------------------------------------------------------------------------- /cats/src/main/scala/scalaz/ioeffect/catz.scala: -------------------------------------------------------------------------------- 1 | package scalaz.ioeffect 2 | 3 | import cats.effect 4 | import cats.effect.Effect 5 | import cats.syntax.all._ 6 | 7 | import scala.util.control.NonFatal 8 | 9 | object catz extends RTS with IOEffectInstance 10 | private[ioeffect] trait IOEffectInstance { 11 | this: RTS => 12 | 13 | implicit val catsEffectInstance: Effect[Task] = new Effect[Task] { 14 | def runAsync[A]( 15 | fa: Task[A] 16 | )(cb: Either[Throwable, A] => effect.IO[Unit]): effect.IO[Unit] = { 17 | val cbZ2C: ExitResult[Throwable, A] => Either[Throwable, A] = { 18 | case ExitResult.Completed(a) => Right(a) 19 | case ExitResult.Failed(t) => Left(t) 20 | case ExitResult.Terminated(t) => Left(t) 21 | } 22 | effect.IO { 23 | unsafePerformIOAsync(fa) { 24 | cb.compose(cbZ2C).andThen(_.unsafeRunAsync(_ => ())) 25 | } 26 | }.attempt.void 27 | } 28 | 29 | def async[A](k: (Either[Throwable, A] => Unit) => Unit): Task[A] = { 30 | val kk = k.compose[ExitResult[Throwable, A] => Unit] { 31 | _.compose[Either[Throwable, A]] { 32 | case Left(t) => ExitResult.Failed(t) 33 | case Right(r) => ExitResult.Completed(r) 34 | } 35 | } 36 | 37 | IO.async(kk) 38 | } 39 | 40 | def suspend[A](thunk: => Task[A]): Task[A] = IO.suspend( 41 | try { 42 | thunk 43 | } catch { 44 | case NonFatal(e) => IO.fail(e) 45 | } 46 | ) 47 | 48 | def raiseError[A](e: Throwable): Task[A] = IO.fail(e) 49 | 50 | def handleErrorWith[A](fa: Task[A])(f: Throwable => Task[A]): Task[A] = 51 | fa.catchAll(f) 52 | 53 | def pure[A](x: A): Task[A] = IO.now(x) 54 | 55 | def flatMap[A, B](fa: Task[A])(f: A => Task[B]): Task[B] = fa.flatMap(f) 56 | 57 | //LOL monad "law" 58 | def tailRecM[A, B](a: A)(f: A => Task[Either[A, B]]): Task[B] = 59 | f(a).flatMap { 60 | case Left(l) => tailRecM(l)(f) 61 | case Right(r) => IO.now(r) 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /cats/src/test/scala/scalaz/ioeffect/IOCatsLawsTest.scala: -------------------------------------------------------------------------------- 1 | package scalaz.ioeffect 2 | 3 | import java.io.{ ByteArrayOutputStream, PrintStream } 4 | 5 | import cats.effect.laws.discipline.EffectTests 6 | import org.typelevel.discipline.scalatest.Discipline 7 | import cats.effect.laws.util.TestContext 8 | import org.typelevel.discipline.Laws 9 | import org.scalatest.prop.Checkers 10 | import org.scalatest.{ FunSuite, Matchers } 11 | 12 | import scalaz.ioeffect.catz._ 13 | import cats.implicits._ 14 | 15 | import scala.util.control.NonFatal 16 | 17 | class IOCatsLawsTest extends FunSuite with Matchers with Checkers with Discipline with IOScalaCheckInstances { 18 | 19 | /** 20 | * Silences `System.err`, only printing the output in case exceptions are 21 | * thrown by the executed `thunk`. 22 | */ 23 | def silenceSystemErr[A](thunk: => A): A = synchronized { 24 | // Silencing System.err 25 | val oldErr = System.err 26 | val outStream = new ByteArrayOutputStream() 27 | val fakeErr = new PrintStream(outStream) 28 | System.setErr(fakeErr) 29 | try { 30 | val result = thunk 31 | System.setErr(oldErr) 32 | result 33 | } catch { 34 | case NonFatal(e) => 35 | System.setErr(oldErr) 36 | // In case of errors, print whatever was caught 37 | fakeErr.close() 38 | val out = outStream.toString("utf-8") 39 | if (out.nonEmpty) oldErr.println(out) 40 | throw e 41 | } 42 | } 43 | 44 | def checkAllAsync(name: String, f: TestContext => Laws#RuleSet): Unit = { 45 | val context = TestContext() 46 | val ruleSet = f(context) 47 | 48 | for ((id, prop) ← ruleSet.all.properties) 49 | test(name + "." + id) { 50 | silenceSystemErr(check(prop)) 51 | } 52 | } 53 | 54 | checkAllAsync("Effect[Task]", implicit e => EffectTests[Task].effect[Int, Int, Int]) 55 | } 56 | -------------------------------------------------------------------------------- /cats/src/test/scala/scalaz/ioeffect/IOScalaCheckInstances.scala: -------------------------------------------------------------------------------- 1 | package scalaz.ioeffect 2 | 3 | import cats.Eq 4 | import cats.effect.laws.util.TestInstances 5 | import cats.syntax.all._ 6 | import org.scalacheck.{ Arbitrary, Cogen, Gen } 7 | 8 | import catz._ 9 | 10 | trait IOScalaCheckInstances extends TestInstances { 11 | 12 | implicit def cogenTask[A]: Cogen[Task[A]] = 13 | Cogen[Unit].contramap(_ => ()) // YOLO 14 | 15 | //Todo: define in terms of `unsafeRunAsync` which John said he'll add 16 | // This for sure doesn't mean that for x: IO[A] y: IO[A], x === y. 17 | // It's nonsensical in a theoretical sense anyway to compare to IO values 18 | // In the presence of side effects, so I consider this more for the sake of 19 | // A test than a "law" in itself. 20 | implicit def catsEQ[A](implicit E: Eq[A]): Eq[Task[A]] = 21 | new Eq[Task[A]] { 22 | def eqv(x: Task[A], y: Task[A]): Boolean = 23 | (tryUnsafePerformIO(x), tryUnsafePerformIO(y)) match { 24 | case (ExitResult.Completed(x1), ExitResult.Completed(y1)) => x1 === y1 25 | case (ExitResult.Failed(x1), ExitResult.Failed(y1)) => x1 === y1 26 | case (ExitResult.Terminated(x1), ExitResult.Terminated(y1)) => 27 | x1 === y1 28 | case _ => false 29 | } 30 | } 31 | 32 | implicit def arbitraryIO[A](implicit A: Arbitrary[A], CG: Cogen[A]): Arbitrary[Task[A]] = { 33 | import Arbitrary._ 34 | def genPure: Gen[Task[A]] = 35 | arbitrary[A].map(IO.now) 36 | 37 | def genApply: Gen[Task[A]] = 38 | arbitrary[A].map(IO.syncThrowable[A](_)) 39 | 40 | def genFail: Gen[Task[A]] = 41 | arbitrary[Throwable].map(IO.fail[Throwable, A]) 42 | 43 | def genAsync: Gen[Task[A]] = 44 | arbitrary[(Either[Throwable, A] => Unit) => Unit] 45 | .map(catsEffectInstance.async[A]) 46 | 47 | def genNestedAsync: Gen[Task[A]] = 48 | arbitrary[(Either[Throwable, Task[A]] => Unit) => Unit] 49 | .map(k => catsEffectInstance.async(k).flatMap(x => x)) 50 | 51 | def genSimpleTask: Gen[Task[A]] = Gen.frequency( 52 | 1 -> genPure, 53 | 1 -> genApply, 54 | 1 -> genFail, 55 | 1 -> genAsync, 56 | 1 -> genNestedAsync, 57 | 1 -> genBindSuspend 58 | ) 59 | 60 | def genBindSuspend: Gen[Task[A]] = 61 | arbitrary[A].map(IO.syncThrowable(_).flatMap(IO.now)) 62 | 63 | def genFlatMap: Gen[Task[A]] = 64 | for { 65 | ioa <- genSimpleTask 66 | f <- arbitrary[A => Task[A]] 67 | } yield ioa.flatMap(f) 68 | 69 | def getMapOne: Gen[Task[A]] = 70 | for { 71 | ioa <- genSimpleTask 72 | f <- arbitrary[A => A] 73 | } yield ioa.map(f) 74 | 75 | def getMapTwo: Gen[Task[A]] = 76 | for { 77 | ioa <- genSimpleTask 78 | f1 <- arbitrary[A => A] 79 | f2 <- arbitrary[A => A] 80 | } yield ioa.map(f1).map(f2) 81 | 82 | Arbitrary( 83 | Gen.frequency( 84 | 5 -> genPure, 85 | 5 -> genApply, 86 | 1 -> genFail, 87 | 5 -> genBindSuspend, 88 | 5 -> genAsync, 89 | 5 -> genNestedAsync, 90 | 5 -> getMapOne, 91 | 5 -> getMapTwo, 92 | 10 -> genFlatMap 93 | ) 94 | ) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/Async.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | /** 5 | * The `Async` class describes the return value of an asynchronous effect 6 | * that is imported into an `IO` value. 7 | * 8 | * Asynchronous effects can return `later`, which represents an uninterruptible 9 | * asynchronous action, `now` which represents a synchronously computed value, 10 | * or `maybeLater`, which represents an interruptible asynchronous action. 11 | */ 12 | sealed abstract class Async[E, A] 13 | object Async { 14 | 15 | val NoOpCanceler: Canceler = _ => () 16 | val NoOpPureCanceler: PureCanceler = _ => IO.unit[Void] 17 | 18 | private val _Later: Async[Nothing, Nothing] = MaybeLater(NoOpCanceler) 19 | 20 | // TODO: Optimize this common case to less overhead with opaque types 21 | final case class Now[E, A](value: ExitResult[E, A]) extends Async[E, A] 22 | final case class MaybeLater[E, A](canceler: Canceler) extends Async[E, A] 23 | final case class MaybeLaterIO[E, A](canceler: PureCanceler) extends Async[E, A] 24 | 25 | /** 26 | * Constructs an `Async` that represents an uninterruptible asynchronous 27 | * action. The action should invoke the callback passed to the handler when 28 | * the value is available or the action has failed. 29 | * 30 | * See `IO.async0` for more information. 31 | */ 32 | final def later[E, A]: Async[E, A] = _Later.asInstanceOf[Async[E, A]] 33 | 34 | /** 35 | * Constructs an `Async` that represents a synchronous return. The 36 | * handler should never invoke the callback. 37 | * 38 | * See `IO.async0` for more information. 39 | */ 40 | final def now[E, A](result: ExitResult[E, A]): Async[E, A] = Now(result) 41 | 42 | /** 43 | * Constructs an `Async` that represents an interruptible asynchronous 44 | * action. The action should invoke the callback passed to the handler when 45 | * the value is available or the action has failed. 46 | * 47 | * The specified canceler, which must be idempotent, should attempt to cancel 48 | * the asynchronous action to avoid wasting resources for an action whose 49 | * results are no longer needed because the fiber computing them has been 50 | * terminated. 51 | */ 52 | final def maybeLater[E, A](canceler: Canceler): Async[E, A] = 53 | MaybeLater(canceler) 54 | 55 | final def maybeLaterIO[E, A](canceler: PureCanceler): Async[E, A] = 56 | MaybeLaterIO(canceler) 57 | } 58 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/Errors.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | import scalaz.\/ 5 | 6 | object Errors { 7 | final case class LostRace(loser: Fiber[_, _] \/ Fiber[_, _]) 8 | extends Exception( 9 | "Lost a race to " + loser.fold(_ => "right", _ => "left") 10 | ) 11 | 12 | final case class TerminatedException(value: Any) 13 | extends Exception( 14 | "The action was interrupted due to a user-defined error: " + value 15 | .toString() 16 | ) 17 | 18 | final case class UnhandledError(error: Any) 19 | extends Exception( 20 | "An error was not handled by a fiber: " + error.toString() 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/ExitResult.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | /** 5 | * A description of the result of executing an `IO` value. The result is either 6 | * completed with a value, failed because of an uncaught `E`, or terminated 7 | * due to interruption or runtime error. 8 | */ 9 | sealed trait ExitResult[E, A] { self => 10 | import ExitResult._ 11 | 12 | final def succeeded: Boolean = self match { 13 | case Completed(_) => true 14 | case Failed(_) => false 15 | case Terminated(_) => false 16 | } 17 | 18 | final def map[B](f: A => B): ExitResult[E, B] = self match { 19 | case Completed(a) => Completed(f(a)) 20 | case Failed(e) => Failed(e) 21 | case Terminated(t) => Terminated(t) 22 | } 23 | 24 | final def failed: Boolean = !succeeded 25 | 26 | final def fold[Z](completed: A => Z, failed: E => Z, interrupted: Throwable => Z): Z = self match { 27 | case Completed(v) => completed(v) 28 | case Failed(e) => failed(e) 29 | case Terminated(e) => interrupted(e) 30 | } 31 | } 32 | object ExitResult { 33 | final case class Completed[E, A](value: A) extends ExitResult[E, A] 34 | final case class Failed[E, A](error: E) extends ExitResult[E, A] 35 | final case class Terminated[E, A](error: Throwable) extends ExitResult[E, A] 36 | } 37 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/Fiber.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | /** 5 | * A fiber is a lightweight thread of execution that never consumes more than a 6 | * whole thread (but may consume much less, depending on contention). Fibers are 7 | * spawned by forking `IO` actions, which, conceptually at least, runs them 8 | * concurrently with the parent `IO` action. 9 | * 10 | * Fibers can be joined, yielding their result other fibers, or interrupted, 11 | * which terminates the fiber with a runtime error. 12 | * 13 | * Fork-Join Identity: fork >=> join = id 14 | * 15 | * {{{ 16 | * for { 17 | * fiber1 <- io1.fork 18 | * fiber2 <- io2.fork 19 | * _ <- fiber1.interrupt(e) 20 | * a <- fiber2.join 21 | * } yield a 22 | * }}} 23 | */ 24 | trait Fiber[E, A] { 25 | 26 | /** 27 | * Joins the fiber, with suspends the joining fiber until the result of the 28 | * fiber has been determined. Attempting to join a fiber that has been or is 29 | * killed before producing its result will result in a catchable error. 30 | */ 31 | def join: IO[E, A] 32 | 33 | /** 34 | * Interrupts the fiber with the specified error. If the fiber has already 35 | * terminated, either successfully or with error, this will resume 36 | * immediately. Otherwise, it will resume when the fiber has been 37 | * successfully interrupted or has produced its result. 38 | */ 39 | def interrupt[E2](t: Throwable): IO[E2, Unit] 40 | } 41 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/IO.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | import scala.annotation.switch 5 | import scala.concurrent.duration._ 6 | import scalaz.{ -\/, @@, \/, \/-, unused, Maybe, Monad } 7 | import scalaz.syntax.either._ 8 | import scalaz.ioeffect.Errors._ 9 | import scalaz.Liskov.<~< 10 | import scalaz.Tags.Parallel 11 | 12 | import scala.concurrent.{ ExecutionContext, Future } 13 | 14 | /** 15 | * An `IO[E, A]` ("Eye-Oh of Eeh Aye") is an immutable data structure that 16 | * describes an effectful action that may fail with an `E`, run forever, or 17 | * produce a single `A` at some point in the future. 18 | * 19 | * Conceptually, this structure is equivalent to `EitherT[F, E, A]` for some 20 | * infallible effect monad `F`, but because monad transformers perform poorly 21 | * in Scala, this structure bakes in the `EitherT` without runtime overhead. 22 | * 23 | * `IO` values are ordinary immutable values, and may be used like any other 24 | * values in purely functional code. Because `IO` values just *describe* 25 | * effects, which must be interpreted by a separate runtime system, they are 26 | * entirely pure and do not violate referential transparency. 27 | * 28 | * `IO` values can efficiently describe the following classes of effects: 29 | * 30 | * * **Pure Values** — `IO.point` 31 | * * **Synchronous Effects** — `IO.sync` 32 | * * **Asynchronous Effects** — `IO.async` 33 | * * **Concurrent Effects** — `io.fork` 34 | * * **Resource Effects** — `io.bracket` 35 | * 36 | * The concurrency model is based on *fibers*, a user-land lightweight thread, 37 | * which permit cooperative multitasking, fine-grained interruption, and very 38 | * high performance with large numbers of concurrently executing fibers. 39 | * 40 | * `IO` values compose with other `IO` values in a variety of ways to build 41 | * complex, rich, interactive applications. See the methods on `IO` for more 42 | * details about how to compose `IO` values. 43 | * 44 | * In order to integrate with Scala, `IO` values must be interpreted into the 45 | * Scala runtime. This process of interpretation executes the effects described 46 | * by a given immutable `IO` value. For more information on interpreting `IO` 47 | * values, see the default interpreter in `RTS` or the safe main function in 48 | * `SafeApp`. 49 | */ 50 | sealed abstract class IO[E, A] { self => 51 | 52 | /** 53 | * Maps an `IO[E, A]` into an `IO[E, B]` by applying the specified `A => B` function 54 | * to the output of this action. Repeated applications of `map` 55 | * (`io.map(f1).map(f2)...map(f10000)`) are guaranteed stack safe to a depth 56 | * of at least 10,000. 57 | */ 58 | final def map[B](f: A => B): IO[E, B] = (self.tag: @switch) match { 59 | case IO.Tags.Point => 60 | val io = self.asInstanceOf[IO.Point[E, A]] 61 | 62 | new IO.Point(() => f(io.value())) 63 | 64 | case IO.Tags.Strict => 65 | val io = self.asInstanceOf[IO.Strict[E, A]] 66 | 67 | new IO.Strict(f(io.value)) 68 | 69 | case IO.Tags.Fail => self.asInstanceOf[IO[E, B]] 70 | 71 | case _ => new IO.FlatMap(self, (a: A) => new IO.Strict(f(a))) 72 | } 73 | 74 | /** 75 | * Creates a composite action that represents this action followed by another 76 | * one that may depend on the value produced by this one. 77 | * 78 | * {{{ 79 | * val parsed = readFile("foo.txt").flatMap(file => parseFile(file)) 80 | * }}} 81 | */ 82 | final def flatMap[B](f0: A => IO[E, B]): IO[E, B] = new IO.FlatMap(self, f0) 83 | 84 | /** 85 | * Forks this action into its own separate fiber, returning immediately 86 | * without the value produced by this action. 87 | * 88 | * The `Fiber[E, A]` returned by this action can be used to interrupt the 89 | * forked fiber with some exception, or to join the fiber to "await" its 90 | * computed value. 91 | * 92 | * {{{ 93 | * for { 94 | * fiber <- subtask.fork 95 | * // Do stuff... 96 | * a <- subtask.join 97 | * } yield a 98 | * }}} 99 | */ 100 | final def fork[E2]: IO[E2, Fiber[E, A]] = new IO.Fork(this, Maybe.empty) 101 | 102 | /** 103 | * A more powerful version of `fork` that allows specifying a handler to be 104 | * invoked on any exceptions that are not handled by the forked fiber. 105 | */ 106 | final def fork0[E2]( 107 | handler: Throwable => IO[Void, Unit] 108 | ): IO[E2, Fiber[E, A]] = 109 | new IO.Fork(this, Maybe.just(handler)) 110 | 111 | /** 112 | * Executes both this action and the specified action in parallel, 113 | * returning a tuple of their results. If either individual action fails, 114 | * then the returned action will fail. 115 | * 116 | * TODO: Replace with optimized primitive. 117 | */ 118 | final def par[B](that: IO[E, B]): IO[E, (A, B)] = 119 | self 120 | .attempt[E] 121 | .raceWith(that.attempt[E])( 122 | { 123 | case (-\/(e), fiberb) => 124 | fiberb.interrupt(TerminatedException(e)) *> IO.fail(e) 125 | case (\/-(a), fiberb) => IO.absolve(fiberb.join).map((b: B) => (a, b)) 126 | }, { 127 | case (-\/(e), fibera) => 128 | fibera.interrupt(TerminatedException(e)) *> IO.fail(e) 129 | case (\/-(b), fibera) => IO.absolve(fibera.join).map((a: A) => (a, b)) 130 | } 131 | ) 132 | 133 | /** 134 | * Races this action with the specified action, returning the first 135 | * result to produce an `A`, whichever it is. If neither action succeeds, 136 | * then the action will be terminated with some error. 137 | */ 138 | final def race(that: IO[E, A]): IO[E, A] = 139 | raceWith(that)( 140 | (a, fiber) => fiber.interrupt(LostRace(\/-(fiber))).const(a), 141 | (a, fiber) => fiber.interrupt(LostRace(-\/(fiber))).const(a) 142 | ) 143 | 144 | /** 145 | * Races this action with the specified action, invoking the 146 | * specified finisher as soon as one value or the other has been computed. 147 | */ 148 | final def raceWith[B, C](that: IO[E, B])( 149 | finishLeft: (A, Fiber[E, B]) => IO[E, C], 150 | finishRight: (B, Fiber[E, A]) => IO[E, C] 151 | ): IO[E, C] = 152 | new IO.Race[E, A, B, C](self, that, finishLeft, finishRight) 153 | 154 | /** 155 | * Executes this action and returns its value, if it succeeds, but 156 | * otherwise executes the specified action. 157 | */ 158 | final def orElse(that: => IO[E, A]): IO[E, A] = 159 | self.attempt.flatMap(_.fold(_ => that, IO.now)) 160 | 161 | /** 162 | * Maps over the error type. This can be used to lift a "smaller" error into 163 | * a "larger" error. 164 | */ 165 | final def leftMap[E2](f: E => E2): IO[E2, A] = 166 | attempt[E2].flatMap { 167 | case -\/(e) => IO.fail[E2, A](f(e)) 168 | case \/-(a) => IO.now[E2, A](a) 169 | } 170 | 171 | /** 172 | * Widens the error type to any supertype. While `leftMap` suffices for this 173 | * purpose, this method is significantly faster for this purpose. 174 | */ 175 | final def widenError[E2](implicit @unused ev: E <~< E2): IO[E2, A] = 176 | self.asInstanceOf[IO[E2, A]] 177 | 178 | /** 179 | * Widens the type to any supertype more efficiently than `map(identity)`. 180 | */ 181 | final def widen[A2](implicit @unused ev: A <~< A2): IO[E, A2] = 182 | self.asInstanceOf[IO[E, A2]] 183 | 184 | /** 185 | * Executes this action, capturing both failure and success and returning 186 | * the result in a `Disjunction`. This method is useful for recovering from 187 | * `IO` actions that may fail. 188 | * 189 | * The error parameter of the returned `IO` may be chosen arbitrarily, since 190 | * it is guaranteed the `IO` action does not raise any errors. 191 | */ 192 | final def attempt[E2]: IO[E2, E \/ A] = (self.tag: @switch) match { 193 | case IO.Tags.Point => 194 | val io = self.asInstanceOf[IO.Point[E, A]] 195 | 196 | new IO.Point(() => \/-(io.value())) 197 | 198 | case IO.Tags.Strict => 199 | val io = self.asInstanceOf[IO.Strict[E, A]] 200 | 201 | new IO.Strict(\/-(io.value)) 202 | 203 | case IO.Tags.SyncEffect => 204 | val io = self.asInstanceOf[IO.SyncEffect[E, A]] 205 | 206 | new IO.SyncEffect(() => \/-(io.effect())) 207 | 208 | case IO.Tags.Fail => 209 | val io = self.asInstanceOf[IO.Fail[E, A]] 210 | 211 | new IO.Strict(-\/(io.error)) 212 | 213 | case _ => new IO.Attempt(self) 214 | } 215 | 216 | /** 217 | * Ignores the error and value of this IO, useful for explicitly acknowledging 218 | * that a cleanup task will have its result ignored. 219 | */ 220 | def ignore: IO[Void, Unit] = attempt[Void].toUnit 221 | 222 | /** 223 | * When this action represents acquisition of a resource (for example, 224 | * opening a file, launching a thread, etc.), `bracket` can be used to ensure 225 | * the acquisition is not interrupted and the resource is released. 226 | * 227 | * The function does two things: 228 | * 229 | * 1. Ensures this action, which acquires the resource, will not be 230 | * interrupted. Of course, acquisition may fail for internal reasons (an 231 | * uncaught exception). 232 | * 2. Ensures the `release` action will not be interrupted, and will be 233 | * executed so long as this action successfully acquires the resource. 234 | * 235 | * In between acquisition and release of the resource, the `use` action is 236 | * executed. 237 | * 238 | * If the `release` action fails, then the entire action will fail even 239 | * if the `use` action succeeds. If this fail-fast behavior is not desired, 240 | * errors produced by the `release` action can be caught and ignored. 241 | * 242 | * {{{ 243 | * openFile("data.json").bracket(closeFile) { file => 244 | * for { 245 | * header <- readHeader(file) 246 | * ... 247 | * } yield result 248 | * } 249 | * }}} 250 | */ 251 | final def bracket[B]( 252 | release: A => IO[Void, Unit] 253 | )(use: A => IO[E, B]): IO[E, B] = 254 | new IO.Bracket(this, (_: ExitResult[E, B], a: A) => release(a), use) 255 | 256 | /** 257 | * A more powerful version of `bracket` that provides information on whether 258 | * or not `use` succeeded to the release action. 259 | */ 260 | final def bracket0[B]( 261 | release: (ExitResult[E, B], A) => IO[Void, Unit] 262 | )(use: A => IO[E, B]): IO[E, B] = 263 | new IO.Bracket(this, release, use) 264 | 265 | /** 266 | * A less powerful variant of `bracket` where the value produced by this 267 | * action is not needed. 268 | */ 269 | final def bracket_[B](release: IO[Void, Unit])(use: IO[E, B]): IO[E, B] = 270 | self.bracket(_ => release)(_ => use) 271 | 272 | /** 273 | * Executes the specified finalizer, whether this action succeeds, fails, or 274 | * is interrupted. 275 | */ 276 | final def ensuring(finalizer: IO[Void, Unit]): IO[E, A] = 277 | IO.unit.bracket(_ => finalizer)(_ => self) 278 | 279 | /** 280 | * Executes the release action only if there was an error. 281 | */ 282 | final def bracketOnError[B]( 283 | release: A => IO[Void, Unit] 284 | )(use: A => IO[E, B]): IO[E, B] = 285 | bracket0( 286 | (r: ExitResult[E, B], a: A) => 287 | r match { 288 | case ExitResult.Failed(_) => release(a) 289 | case ExitResult.Terminated(_) => release(a) 290 | case _ => IO.unit 291 | } 292 | )(use) 293 | 294 | /** 295 | * Runs the specified cleanup action if this action errors, providing the 296 | * error to the cleanup action. The cleanup action will not be interrupted. 297 | */ 298 | final def onError(cleanup: Throwable \/ E => IO[Void, Unit]): IO[E, A] = 299 | IO.unit[E] 300 | .bracket0( 301 | (r: ExitResult[E, A], a: Unit) => 302 | r match { 303 | case ExitResult.Failed(e) => cleanup(e.right) 304 | case ExitResult.Terminated(e) => cleanup(e.left) 305 | case _ => IO.unit 306 | } 307 | )(_ => self) 308 | 309 | /** 310 | * Supervises this action, which ensures that any fibers that are forked by 311 | * the action are interrupted with the specified error when this action 312 | * completes. 313 | */ 314 | final def supervised(error: Throwable): IO[E, A] = new IO.Supervise(self, error) 315 | 316 | /** 317 | * Performs this action non-interruptibly. This will prevent the action from 318 | * being terminated externally, but the action may fail for internal reasons 319 | * (e.g. an uncaught error) or terminate due to defect. 320 | */ 321 | final def uninterruptibly: IO[E, A] = new IO.Uninterruptible(self) 322 | 323 | /** 324 | * Recovers from all errors. 325 | * 326 | * {{{ 327 | * openFile("config.json").catchAll(_ => IO.now(defaultConfig)) 328 | * }}} 329 | */ 330 | final def catchAll[E2](h: E => IO[E2, A]): IO[E2, A] = 331 | self.attempt[E2].flatMap { 332 | case -\/(e) => h(e) 333 | case \/-(a) => IO.now[E2, A](a) 334 | } 335 | 336 | /** 337 | * Recovers from some or all of the error cases. 338 | * 339 | * {{{ 340 | * openFile("data.json").catchSome { 341 | * case FileNotFoundException(_) => openFile("backup.json") 342 | * } 343 | * }}} 344 | */ 345 | final def catchSome(pf: PartialFunction[E, IO[E, A]]): IO[E, A] = { 346 | def tryRescue(t: E): IO[E, A] = 347 | if (pf.isDefinedAt(t)) pf(t) else IO.fail(t) 348 | 349 | self.attempt[E].flatMap(_.fold(tryRescue, IO.now)) 350 | } 351 | 352 | /** 353 | * Maps this action to the specified constant while preserving the 354 | * effects of this action. 355 | */ 356 | final def const[B](b: => B): IO[E, B] = self.map(_ => b) 357 | 358 | /** 359 | * A variant of `flatMap` that ignores the value produced by this action. 360 | */ 361 | final def *>[B](io: => IO[E, B]): IO[E, B] = self.flatMap(_ => io) 362 | 363 | /** 364 | * Sequences the specified action after this action, but ignores the 365 | * value produced by the action. 366 | */ 367 | final def <*[B](io: => IO[E, B]): IO[E, A] = self.flatMap(io.const(_)) 368 | 369 | /** 370 | * Sequentially zips this effect with the specified effect using the 371 | * specified combiner function. 372 | */ 373 | final def zipWith[B, C](that: IO[E, B])(f: (A, B) => C): IO[E, C] = 374 | self.flatMap(a => that.map(b => f(a, b))) 375 | 376 | /** 377 | * Repeats this action forever (until the first error). 378 | */ 379 | final def forever[B]: IO[E, B] = self *> self.forever 380 | 381 | /** 382 | * Retries continuously until this action succeeds. 383 | */ 384 | final def retry: IO[E, A] = self.orElse(retry) 385 | 386 | /** 387 | * Retries this action the specified number of times, until the first success. 388 | * Note that the action will always be run at least once, even if `n < 1`. 389 | */ 390 | final def retryN(n: Int): IO[E, A] = 391 | retryBackoff(n, 1.0, Duration.fromNanos(0)) 392 | 393 | /** 394 | * Retries continuously until the action succeeds or the specified duration 395 | * elapses. 396 | */ 397 | final def retryFor(duration: Duration): IO[E, Maybe[A]] = 398 | retry 399 | .map(Maybe.just[A]) 400 | .race(IO.sleep[E](duration) *> IO.now[E, Maybe[A]](Maybe.empty[A])) 401 | 402 | /** 403 | * Retries continuously, increasing the duration between retries each time by 404 | * the specified multiplication factor, and stopping after the specified upper 405 | * limit on retries. 406 | */ 407 | final def retryBackoff(n: Int, factor: Double, duration: Duration): IO[E, A] = 408 | if (n <= 1) self 409 | else 410 | self.orElse( 411 | IO.sleep(duration) *> retryBackoff(n - 1, factor, duration * factor) 412 | ) 413 | 414 | /** 415 | * Repeats this action continuously until the first error, with the specified 416 | * interval between each full execution. 417 | */ 418 | final def repeat[B](interval: Duration): IO[E, B] = 419 | self *> IO.sleep(interval) *> repeat(interval) 420 | 421 | /** 422 | * Repeats this action continuously until the first error, with the specified 423 | * interval between the start of each full execution. Note that if the 424 | * execution of this action takes longer than the specified interval, then the 425 | * action will instead execute as quickly as possible, but not 426 | * necessarily at the specified interval. 427 | */ 428 | final def repeatFixed[B](interval: Duration): IO[E, B] = 429 | repeatFixed0(IO.sync(System.nanoTime()))(interval) 430 | 431 | final def repeatFixed0[B]( 432 | nanoTime: IO[Void, Long] 433 | )(interval: Duration): IO[E, B] = 434 | IO.flatten(nanoTime[E].flatMap { start => 435 | val gapNs = interval.toNanos 436 | 437 | def tick[C](n: Int): IO[E, C] = 438 | self *> nanoTime[E].flatMap { now => 439 | val await = ((start + n * gapNs) - now).max(0L) 440 | 441 | IO.sleep(await.nanoseconds) *> tick(n + 1) 442 | } 443 | 444 | tick(1) 445 | }) 446 | 447 | /** 448 | * Maps this action to one producing unit, but preserving the effects of 449 | * this action. 450 | */ 451 | final def toUnit: IO[E, Unit] = const(()) 452 | 453 | /** 454 | * Calls the provided function with the result of this action, and 455 | * sequences the resulting action after this action, but ignores the 456 | * value produced by the action. 457 | * 458 | * {{{ 459 | * readFile("data.json").peek(putStrLn) 460 | * }}} 461 | */ 462 | final def peek[B](f: A => IO[E, B]): IO[E, A] = 463 | self.flatMap(a => f(a).const(a)) 464 | 465 | /** 466 | * Times out this action by the specified duration. 467 | * 468 | * {{{ 469 | * action.timeout(1.second) 470 | * }}} 471 | */ 472 | final def timeout(duration: Duration): IO[E, Maybe[A]] = { 473 | val timer = IO.now[E, Maybe[A]](Maybe.empty[A]) 474 | 475 | self.map(Maybe.just[A]).race(timer.delay(duration)) 476 | } 477 | 478 | /** 479 | * Returns a new action that executes this one and times the execution. 480 | */ 481 | final def timed: IO[E, (Duration, A)] = timed0(IO.sync(System.nanoTime())) 482 | 483 | /** 484 | * A more powerful variation of `timed` that allows specifying the clock. 485 | */ 486 | final def timed0(nanoTime: IO[E, Long]): IO[E, (Duration, A)] = 487 | summarized[Long, Duration]((start, end) => Duration.fromNanos(end - start))( 488 | nanoTime 489 | ) 490 | 491 | /** 492 | * Summarizes a action by computing some value before and after execution, and 493 | * then combining the values to produce a summary, together with the result of 494 | * execution. 495 | */ 496 | final def summarized[B, C](f: (B, B) => C)(summary: IO[E, B]): IO[E, (C, A)] = 497 | for { 498 | start <- summary 499 | value <- self 500 | end <- summary 501 | } yield (f(start, end), value) 502 | 503 | /** 504 | * Delays this action by the specified amount of time. 505 | */ 506 | final def delay(duration: Duration): IO[E, A] = 507 | IO.sleep(duration) *> self 508 | 509 | /** 510 | * Runs this action in a new fiber, resuming when the fiber terminates. 511 | */ 512 | final def run[E2]: IO[E2, ExitResult[E, A]] = new IO.Run(self) 513 | 514 | /** 515 | * Lets the user define separate continuations for the case of failure (`err`) or 516 | * success (`succ`). Executes this action and based on the result executes 517 | * the next action, `err` or `succ`. 518 | */ 519 | final def redeem[E2, B](err: E => IO[E2, B], succ: A => IO[E2, B]): IO[E2, B] = 520 | self.attempt[E2].flatMap(_.fold(err, succ)) 521 | 522 | /** 523 | * Fold errors and values to some `B`, resuming with `IO[Void, B]` - 524 | * a slightly less powerful version of `redeem`. 525 | */ 526 | final def fold[E2, B](err: E => B, succ: A => B): IO[E2, B] = 527 | self.attempt[E2].map(_.fold(err, succ)) 528 | 529 | /** 530 | * For some monad F and some error type E, lift this IO 531 | * into F if there is a monadIO instance for F 532 | */ 533 | final def liftIO[F[_]: Monad](implicit M: MonadIO[F, E]): F[A] = M.liftIO(self) 534 | 535 | /** 536 | * An integer that identifies the term in the `IO` sum type to which this 537 | * instance belongs (e.g. `IO.Tags.Point`). 538 | */ 539 | def tag: Int 540 | } 541 | 542 | object IO extends IOInstances { 543 | type Par[e, a] = IO[e, a] @@ Parallel 544 | 545 | final object Tags { 546 | final val FlatMap = 0 547 | final val Point = 1 548 | final val Strict = 2 549 | final val SyncEffect = 3 550 | final val Fail = 4 551 | final val AsyncEffect = 5 552 | final val AsyncIOEffect = 6 553 | final val Attempt = 7 554 | final val Fork = 8 555 | final val Race = 9 556 | final val Suspend = 10 557 | final val Bracket = 11 558 | final val Uninterruptible = 12 559 | final val Sleep = 13 560 | final val Supervise = 14 561 | final val Terminate = 15 562 | final val Supervisor = 16 563 | final val Run = 17 564 | } 565 | final class FlatMap[E, A0, A] private[IO] (val io: IO[E, A0], val flatMapper: A0 => IO[E, A]) extends IO[E, A] { 566 | override def tag: Int = Tags.FlatMap 567 | } 568 | 569 | final class Point[E, A] private[IO] (val value: () => A) extends IO[E, A] { 570 | override def tag: Int = Tags.Point 571 | } 572 | 573 | final class Strict[E, A] private[IO] (val value: A) extends IO[E, A] { 574 | override def tag: Int = Tags.Strict 575 | } 576 | 577 | final class SyncEffect[E, A] private[IO] (val effect: () => A) extends IO[E, A] { 578 | override def tag: Int = Tags.SyncEffect 579 | } 580 | 581 | final class Fail[E, A] private[IO] (val error: E) extends IO[E, A] { 582 | override def tag: Int = Tags.Fail 583 | } 584 | 585 | final class AsyncEffect[E, A] private[IO] (val register: (ExitResult[E, A] => Unit) => Async[E, A]) extends IO[E, A] { 586 | override def tag: Int = Tags.AsyncEffect 587 | } 588 | 589 | final class AsyncIOEffect[E, A] private[IO] (val register: (ExitResult[E, A] => Unit) => IO[E, Unit]) 590 | extends IO[E, A] { 591 | override def tag: Int = Tags.AsyncIOEffect 592 | } 593 | 594 | final class Attempt[E1, E2, A] private[IO] (val value: IO[E1, A]) extends IO[E2, E1 \/ A] { 595 | override def tag: Int = Tags.Attempt 596 | } 597 | 598 | final class Fork[E1, E2, A] private[IO] (val value: IO[E1, A], val handler: Maybe[Throwable => IO[Void, Unit]]) 599 | extends IO[E2, Fiber[E1, A]] { 600 | override def tag: Int = Tags.Fork 601 | } 602 | 603 | final class Race[E, A0, A1, A] private[IO] ( 604 | val left: IO[E, A0], 605 | val right: IO[E, A1], 606 | val finishLeft: (A0, Fiber[E, A1]) => IO[E, A], 607 | val finishRight: (A1, Fiber[E, A0]) => IO[E, A] 608 | ) extends IO[E, A] { 609 | override def tag: Int = Tags.Race 610 | } 611 | 612 | final class Suspend[E, A] private[IO] (val value: () => IO[E, A]) extends IO[E, A] { 613 | override def tag: Int = Tags.Suspend 614 | } 615 | 616 | final class Bracket[E, A, B] private[IO] ( 617 | val acquire: IO[E, A], 618 | val release: (ExitResult[E, B], A) => IO[Void, Unit], 619 | val use: A => IO[E, B] 620 | ) extends IO[E, B] { 621 | override def tag: Int = Tags.Bracket 622 | } 623 | 624 | final class Uninterruptible[E, A] private[IO] (val io: IO[E, A]) extends IO[E, A] { 625 | override def tag: Int = Tags.Uninterruptible 626 | } 627 | 628 | final class Sleep[E] private[IO] (val duration: Duration) extends IO[E, Unit] { 629 | override def tag: Int = Tags.Sleep 630 | } 631 | 632 | final class Supervise[E, A] private[IO] (val value: IO[E, A], val error: Throwable) extends IO[E, A] { 633 | override def tag: Int = Tags.Supervise 634 | } 635 | 636 | final class Terminate[E, A] private[IO] (val cause: Throwable) extends IO[E, A] { 637 | override def tag: Int = Tags.Terminate 638 | } 639 | 640 | final class Supervisor[E] private[IO] () extends IO[E, Throwable => IO[Void, Unit]] { 641 | override def tag: Int = Tags.Supervisor 642 | } 643 | 644 | final class Run[E1, E2, A] private[IO] (val value: IO[E1, A]) extends IO[E2, ExitResult[E1, A]] { 645 | override def tag: Int = Tags.Run 646 | } 647 | 648 | /** 649 | * Lifts a strictly evaluated value into the `IO` monad. 650 | */ 651 | final def now[E, A](a: A): IO[E, A] = new Strict(a) 652 | 653 | /** 654 | * Lifts a non-strictly evaluated value into the `IO` monad. Do not use this 655 | * function to capture effectful code. The result is undefined but may 656 | * include duplicated effects. 657 | */ 658 | final def point[E, A](a: => A): IO[E, A] = new Point(() => a) 659 | 660 | /** 661 | * Creates an `IO` value that represents failure with the specified error. 662 | * The moral equivalent of `throw` for pure code. 663 | */ 664 | final def fail[E, A](error: E): IO[E, A] = new Fail(error) 665 | 666 | /** 667 | * Strictly-evaluated unit lifted into the `IO` monad. 668 | */ 669 | final def unit[E]: IO[E, Unit] = Unit.asInstanceOf[IO[E, Unit]] 670 | 671 | /** 672 | * Sleeps for the specified duration. This is always asynchronous. 673 | */ 674 | final def sleep[E](duration: Duration): IO[E, Unit] = new Sleep(duration) 675 | 676 | /** 677 | * Supervises the specified action, which ensures that any actions directly 678 | * forked by the action are killed with the specified error upon the action's 679 | * own termination. 680 | */ 681 | final def supervise[E, A](io: IO[E, A], error: Throwable): IO[E, A] = new Supervise(io, error) 682 | 683 | /** 684 | * Flattens a nested action. 685 | */ 686 | final def flatten[E, A](io: IO[E, IO[E, A]]): IO[E, A] = io.flatMap(a => a) 687 | 688 | /** 689 | * Lazily produces an `IO` value whose construction may have actional costs 690 | * that should be be deferred until evaluation. 691 | * 692 | * Do not use this method to effectfully construct `IO` values. The results 693 | * will be undefined and most likely involve the physical explosion of your 694 | * computer in a heap of rubble. 695 | */ 696 | final def suspend[E, A](io: => IO[E, A]): IO[E, A] = new Suspend(() => io) 697 | 698 | /** 699 | * Terminates the fiber executing this action, running all finalizers. 700 | */ 701 | final def terminate[E, A](t: Throwable): IO[E, A] = new Terminate(t) 702 | 703 | /** 704 | * Imports a synchronous effect into a pure `IO` value. 705 | * 706 | * {{{ 707 | * val nanoTime: IO[Void, Long] = IO.sync(System.nanoTime()) 708 | * }}} 709 | */ 710 | final def sync[E, A](effect: => A): IO[E, A] = new SyncEffect(() => effect) 711 | 712 | /** 713 | * 714 | * Imports a synchronous effect into a pure `IO` value, translating any 715 | * throwables into a `Throwable` failure in the returned value. 716 | * 717 | * {{{ 718 | * def putStrLn(line: String): IO[Throwable, Unit] = IO.syncThrowable(println(line)) 719 | * }}} 720 | */ 721 | final def syncThrowable[A](effect: => A): IO[Throwable, A] = 722 | IO.suspend { 723 | try { 724 | val a = effect 725 | IO.sync(a) 726 | } catch { 727 | case t: Throwable => IO.fail(t) 728 | } 729 | } 730 | 731 | /** 732 | * 733 | * Imports a synchronous effect into a pure `IO` value, translating any 734 | * exceptions into an `Exception` failure in the returned value. 735 | * 736 | * {{{ 737 | * def putStrLn(line: String): IO[Exception, Unit] = IO.syncException(println(line)) 738 | * }}} 739 | */ 740 | final def syncException[A](effect: => A): IO[Exception, A] = 741 | syncCatch(effect) { 742 | case e: Exception => e 743 | } 744 | 745 | /** 746 | * Safely imports an exception-throwing synchronous effect into a pure `IO` 747 | * value, translating the specified throwables into `E` with the provided 748 | * user-defined function. 749 | */ 750 | final def syncCatch[E, A]( 751 | effect: => A 752 | )(f: PartialFunction[Throwable, E]): IO[E, A] = 753 | IO.absolve(IO.sync(try { 754 | val result = effect 755 | 756 | result.right 757 | } catch { case t: Throwable if f.isDefinedAt(t) => f(t).left })) 758 | 759 | /** 760 | * Imports an asynchronous effect into a pure `IO` value. See `async0` for 761 | * the more expressive variant of this function. 762 | */ 763 | final def async[E, A](register: (ExitResult[E, A] => Unit) => Unit): IO[E, A] = 764 | new AsyncEffect(callback => { 765 | register(callback) 766 | 767 | Async.later[E, A] 768 | }) 769 | 770 | /** 771 | * Imports an asynchronous effect into a pure `IO` value. This formulation is 772 | * necessary when the effect is itself expressed in terms of `IO`. 773 | */ 774 | final def asyncPure[E, A](register: (ExitResult[E, A] => Unit) => IO[E, Unit]): IO[E, A] = new AsyncIOEffect(register) 775 | 776 | /** 777 | * Imports an asynchronous effect into a pure `IO` value. The effect has the 778 | * option of returning the value synchronously, which is useful in cases 779 | * where it cannot be determined if the effect is synchronous or asynchronous 780 | * until the effect is actually executed. The effect also has the option of 781 | * returning a canceler, which will be used by the runtime to cancel the 782 | * asynchronous effect if the fiber executing the effect is interrupted. 783 | */ 784 | final def async0[E, A](register: (ExitResult[E, A] => Unit) => Async[E, A]): IO[E, A] = new AsyncEffect(register) 785 | 786 | /** 787 | * Returns a action that will never produce anything. The moral 788 | * equivalent of `while(true) {}`, only without the wasted CPU cycles. 789 | */ 790 | final def never[E, A]: IO[E, A] = Never.asInstanceOf[IO[E, A]] 791 | 792 | /** 793 | * Submerges the error case of a disjunction into the `IO`. The inverse 794 | * operation of `IO.attempt`. 795 | */ 796 | final def absolve[E, A](v: IO[E, E \/ A]): IO[E, A] = 797 | v.flatMap { 798 | case -\/(e) => IO.fail(e) 799 | case \/-(a) => IO.now(a) 800 | } 801 | 802 | /** 803 | * Retrieves the supervisor associated with the fiber running the action 804 | * returned by this method. 805 | */ 806 | def supervisor[E]: IO[E, Throwable => IO[Void, Unit]] = new Supervisor() 807 | 808 | /** 809 | * Requires that the given `IO[E, Maybe[A]\]` contain a value. If there is no 810 | * value, then the specified error will be raised. 811 | */ 812 | final def require[E, A](error: E): IO[E, Maybe[A]] => IO[E, A] = 813 | (io: IO[E, Maybe[A]]) => io.flatMap(_.cata(IO.now[E, A](_), IO.fail[E, A](error))) 814 | 815 | /** 816 | * Convert from Future (lifted into IO eagerly via `now`, or delayed via 817 | * `point`) to a `Task`. Futures are inefficient and unsafe: this is provided 818 | * only as a convenience for integrating with legacy systems. 819 | */ 820 | final def fromFuture[E, A](io: Task[Future[A]])(ec: ExecutionContext): Task[A] = 821 | io.attempt.flatMap { f => 822 | IO.async { cb => 823 | f.fold( 824 | t => cb(ExitResult.Failed(t)), 825 | _.onComplete( 826 | t => 827 | cb(t match { 828 | case scala.util.Success(a) => ExitResult.Completed(a) 829 | case scala.util.Failure(t) => ExitResult.Failed(t) 830 | }) 831 | )(ec) 832 | ) 833 | } 834 | } 835 | 836 | // TODO: Make this fast, generalize from `Unit` to `A: Semigroup`, 837 | // and use `IList` instead of `List`. 838 | def forkAll[E2](l: List[IO[E2, Unit]]): IO[E2, Unit] = l match { 839 | case Nil => IO.unit[E2] 840 | case x :: xs => x.fork.toUnit *> forkAll(xs) 841 | } 842 | 843 | private final val Never: IO[Nothing, Any] = 844 | IO.async[Nothing, Any] { (k: (ExitResult[Nothing, Any]) => Unit) => 845 | } 846 | 847 | private final val Unit: IO[Nothing, Unit] = now(()) 848 | } 849 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/IOInstances.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 John A. De Goes. All rights reserved. 2 | package scalaz 3 | package ioeffect 4 | 5 | abstract class IOInstances extends IOInstances1 { 6 | // cached for efficiency 7 | implicit val taskInstances: MonadError[Task, Throwable] with BindRec[Task] with Plus[Task] = 8 | new IOMonadError[Throwable] with IOPlus[Throwable] 9 | 10 | implicit val taskParAp: Applicative[Task.Par] = new IOParApplicative[Throwable] 11 | 12 | implicit val ioUnitMonadPlus: MonadPlus[IO[Unit, ?]] with BindRec[IO[Unit, ?]] = new IOUnitMonadPlus 13 | 14 | } 15 | 16 | sealed abstract class IOInstances1 extends IOInstance2 { 17 | implicit def ioMonoidInstances[E: Monoid] 18 | : MonadError[IO[E, ?], E] with BindRec[IO[E, ?]] with Bifunctor[IO] with MonadPlus[IO[E, ?]] = 19 | new IOMonadPlus[E] with IOBifunctor 20 | 21 | implicit def ioParAp[E]: Applicative[IO.Par[E, ?]] = new IOParApplicative[E] 22 | } 23 | 24 | sealed abstract class IOInstance2 { 25 | implicit def ioInstances[E]: MonadError[IO[E, ?], E] with BindRec[IO[E, ?]] with Bifunctor[IO] with Plus[IO[E, ?]] = 26 | new IOMonadError[E] with IOPlus[E] with IOBifunctor 27 | } 28 | 29 | private class IOMonad[E] extends Monad[IO[E, ?]] with BindRec[IO[E, ?]] { 30 | override def map[A, B](fa: IO[E, A])(f: A => B): IO[E, B] = fa.map(f) 31 | override def point[A](a: => A): IO[E, A] = IO.point(a) 32 | override def bind[A, B](fa: IO[E, A])(f: A => IO[E, B]): IO[E, B] = fa.flatMap(f) 33 | override def tailrecM[A, B](f: A => IO[E, A \/ B])(a: A): IO[E, B] = 34 | f(a).flatMap(_.fold(tailrecM(f), point(_))) 35 | 36 | // backport https://github.com/scalaz/scalaz/pull/1894 37 | override def apply2[A, B, C](fa: => IO[E, A], fb: => IO[E, B])(f: (A, B) => C): IO[E, C] = { 38 | val fb0 = Need(fb) 39 | bind(fa)(a => map(fb0.value)(b => f(a, b))) 40 | } 41 | } 42 | 43 | private class IOMonadError[E] extends IOMonad[E] with MonadError[IO[E, ?], E] { 44 | override def handleError[A](fa: IO[E, A])(f: E => IO[E, A]): IO[E, A] = fa.catchAll(f) 45 | override def raiseError[A](e: E): IO[E, A] = IO.fail(e) 46 | } 47 | 48 | // lossy, throws away errors using the "first success" interpretation of Plus 49 | private trait IOPlus[E] extends Plus[IO[E, ?]] { 50 | override def plus[A](a: IO[E, A], b: => IO[E, A]): IO[E, A] = a.catchAll(_ => b) 51 | } 52 | private class IOUnitMonadPlus extends IOMonadError[Unit] with IOPlus[Unit] with MonadPlus[IO[Unit, ?]] { 53 | override def empty[A]: IO[Unit, A] = raiseError(()) 54 | } 55 | 56 | private class IOMonadPlus[E: Monoid] extends IOMonadError[E] with MonadPlus[IO[E, ?]] { 57 | override def plus[A](a: IO[E, A], b: => IO[E, A]): IO[E, A] = 58 | a.catchAll { e1 => 59 | b.catchAll { e2 => 60 | IO.fail(Monoid[E].append(e1, e2)) 61 | } 62 | } 63 | override def empty[A]: IO[E, A] = raiseError(Monoid[E].zero) 64 | } 65 | 66 | private trait IOBifunctor extends Bifunctor[IO] { 67 | override def bimap[A, B, C, D](fab: IO[A, B])(f: A => C, g: B => D): IO[C, D] = 68 | IO.absolve(fab.attempt.map(_.bimap(f, g))) 69 | } 70 | 71 | private class IOParApplicative[E] extends Applicative[IO.Par[E, ?]] { 72 | override def point[A](a: => A): IO.Par[E, A] = Tag(IO.point(a)) 73 | override def map[A, B](fa: IO.Par[E, A])(f: A => B): IO.Par[E, B] = 74 | Tag(Tag.unwrap(fa).map(f)) 75 | 76 | override def ap[A, B](fa: => IO.Par[E, A])(f: => IO.Par[E, A => B]): IO.Par[E, B] = 77 | apply2(fa, f)((a, abc) => abc(a)) 78 | override def apply2[A, B, C]( 79 | fa: => IO.Par[E, A], 80 | fb: => IO.Par[E, B] 81 | )(f: (A, B) => C): IO.Par[E, C] = 82 | Tag(Tag.unwrap(fa).par(Tag.unwrap(fb)).map(f.tupled)) 83 | } 84 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/IORef.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | import java.util.concurrent.atomic.AtomicReference 5 | 6 | /** 7 | * A mutable atomic reference for the `IO` monad. This is the `IO` equivalent of 8 | * a volatile `var`, augmented with atomic operations, which make it useful as a 9 | * reasonably efficient (if low-level) concurrency primitive. 10 | * 11 | * {{{ 12 | * for { 13 | * ref <- IORef(2) 14 | * v <- ref.modify(_ + 3) 15 | * _ <- putStrLn("Value = " + v.debug) // Value = 5 16 | * } yield () 17 | * }}} 18 | */ 19 | final class IORef[A] private (private val value: AtomicReference[A]) extends AnyVal { 20 | 21 | /** 22 | * Reads the value from the `IORef`. 23 | */ 24 | final def read[E]: IO[E, A] = IO.sync(value.get) 25 | 26 | /** 27 | * Writes a new value to the `IORef`, with a guarantee of immediate 28 | * consistency (at some cost to performance). 29 | */ 30 | final def write[E](a: A): IO[E, Unit] = IO.sync(value.set(a)) 31 | 32 | /** 33 | * Writes a new value to the `IORef` without providing a guarantee of 34 | * immediate consistency. 35 | */ 36 | final def writeLater[E](a: A): IO[E, Unit] = IO.sync(value.lazySet(a)) 37 | 38 | /** 39 | * Attempts to write a new value to the `IORef`, but aborts immediately under 40 | * concurrent modification of the value by other fibers. 41 | */ 42 | final def tryWrite[E](a: A): IO[E, Boolean] = 43 | IO.sync(value.compareAndSet(value.get, a)) 44 | 45 | /** 46 | * Atomically modifies the `IORef` with the specified function. This is not 47 | * implemented in terms of `modifyFold` purely for performance reasons. 48 | */ 49 | final def modify[E](f: A => A): IO[E, A] = IO.sync { 50 | var loop = true 51 | var next: A = null.asInstanceOf[A] 52 | 53 | while (loop) { 54 | val current = value.get 55 | 56 | next = f(current) 57 | 58 | loop = !value.compareAndSet(current, next) 59 | } 60 | 61 | next 62 | } 63 | 64 | /** 65 | * Atomically modifies the `IORef` with the specified function, which computes 66 | * a return value for the modification. This is a more powerful version of 67 | * `modify`. 68 | */ 69 | final def modifyFold[E, B](f: A => (B, A)): IO[E, B] = IO.sync { 70 | var loop = true 71 | var b: B = null.asInstanceOf[B] 72 | 73 | while (loop) { 74 | val current = value.get 75 | 76 | val tuple = f(current) 77 | 78 | b = tuple._1 79 | 80 | loop = !value.compareAndSet(current, tuple._2) 81 | } 82 | 83 | b 84 | } 85 | 86 | /** 87 | * Compares and sets the value of the `IORef` if and only if it is `eq` to the 88 | * specified value. Returns whether or not the ref was modified. 89 | */ 90 | final def compareAndSet[E](prev: A, next: A): IO[E, Boolean] = 91 | IO.sync(value.compareAndSet(prev, next)) 92 | } 93 | 94 | object IORef { 95 | 96 | /** 97 | * Creates a new `IORef` with the specified value. 98 | */ 99 | final def apply[E, A](a: A): IO[E, IORef[A]] = 100 | IO.sync(new IORef[A](new AtomicReference(a))) 101 | } 102 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/MonadIO.scala: -------------------------------------------------------------------------------- 1 | package scalaz.ioeffect 2 | 3 | import scalaz.Monad 4 | import scalaz._ 5 | import Scalaz._ 6 | 7 | /*** 8 | * Monads in which `IO` computations may be embedded. Any monad built by applying a sequence of 9 | * monad transformers to the `IO` monad will be an instance of this class. Instances should satisfy the following laws, 10 | * which state that `liftIO` is a transformer of monads: 11 | * 12 | * liftIO . return = return 13 | * liftIO (m >>= f) = liftIO m >>= (liftIO . f) 14 | * 15 | * @tparam M - the monad in which to lift 16 | * @tparam E - the Error dependency corresponding with the IO to be lifted 17 | */ 18 | trait MonadIO[M[_], E] { 19 | 20 | /** 21 | * Lift a computation from the `IO` monad into `M` 22 | */ 23 | def liftIO[A](io: IO[E, A])(implicit M: Monad[M]): M[A] 24 | 25 | trait MonadIOLaw { 26 | 27 | def leftIdentity[A](a: A)(implicit MA: Equal[M[A]], M: MonadIO[M, E], MM: Monad[M]): Boolean = 28 | MA.equal(a.pure[M], M.liftIO(IO.now(a))) 29 | 30 | def distributivity[A, B]( 31 | f: A => IO[E, B], 32 | io: IO[E, A] 33 | )(implicit MB: Equal[M[B]], M: MonadIO[M, E], MM: Monad[M]): Boolean = 34 | MB.equal(M.liftIO(io.flatMap(f)), M.liftIO(io).flatMap(a => M.liftIO(f(a)))) 35 | } 36 | def monadIOLaw = new MonadIOLaw {} 37 | 38 | } 39 | 40 | object MonadIO extends MonadIOInstances { 41 | def apply[M[_], E](implicit M: MonadIO[M, E]): MonadIO[M, E] = M 42 | 43 | import Isomorphism._ 44 | 45 | def fromIso[F[_], G[_], E](D: F <~> G)(implicit E: MonadIO[G, E], M1: Monad[G]): MonadIO[F, E] = 46 | new IsomorphismMonadIO[F, G, E] { 47 | override def G: MonadIO[G, E] = E 48 | override def M: Monad[G] = M1 49 | override def iso: F <~> G = D 50 | } 51 | } 52 | 53 | private[ioeffect] abstract class IsomorphismMonadIO[F[_], G[_], E] extends MonadIO[F, E] { 54 | implicit def G: MonadIO[G, E] 55 | implicit def M: Monad[G] 56 | 57 | import Isomorphism._ 58 | def iso: F <~> G 59 | 60 | override def liftIO[A](ioa: IO[E, A])(implicit F: Monad[F]): F[A] = 61 | iso.from(G.liftIO(ioa)) 62 | } 63 | 64 | private[ioeffect] sealed abstract class MonadIOInstances extends MonadIOInstances1 { 65 | 66 | implicit val taskMonadIO: MonadIO[Task, Throwable] = new MonadIO[Task, Throwable] { 67 | override def liftIO[A](io: IO[Throwable, A])(implicit M: Monad[Task]): Task[A] = io 68 | } 69 | 70 | implicit def identityTMonadIO[M[_], E](implicit M: MonadIO[M, E], M1: Monad[M]): MonadIO[IdT[M, ?], E] = 71 | new MonadIO[IdT[M, ?], E] { 72 | def liftIO[A](io: IO[E, A])(implicit M2: Monad[IdT[M, ?]]): IdT[M, A] = IdT(M.liftIO(io)) 73 | } 74 | 75 | implicit def contTMonadIO[M[_], R, E](implicit M: MonadIO[M, E], M1: Monad[M]): MonadIO[ContT[M, R, ?], E] = 76 | new MonadIO[ContT[M, R, ?], E] { 77 | def liftIO[A](io: IO[E, A])(implicit M2: Monad[ContT[M, R, ?]]): ContT[M, R, A] = 78 | ContT(M.liftIO(io).flatMap) 79 | } 80 | 81 | implicit def readerTMonadIO[F[_], W, E](implicit F: MonadIO[F, E], M: Monad[F]): MonadIO[ReaderT[F, W, ?], E] = 82 | new MonadIO[ReaderT[F, W, ?], E] { 83 | override def liftIO[A](io: IO[E, A])(implicit M1: Monad[ReaderT[F, W, ?]]): ReaderT[F, W, A] = 84 | ReaderT(_ => F.liftIO(io)) 85 | } 86 | 87 | implicit def stateTMonadIO[F[_], S, E](implicit F: MonadIO[F, E], M: Monad[F]): MonadIO[StateT[F, S, ?], E] = 88 | new MonadIO[StateT[F, S, ?], E] { 89 | override def liftIO[A](io: IO[E, A])(implicit M1: Monad[StateT[F, S, ?]]): StateT[F, S, A] = 90 | StateT(s => F.liftIO(io).map(a => (s, a))) 91 | } 92 | 93 | implicit def writerTMonadIO[F[_], W, E]( 94 | implicit F: MonadIO[F, E], 95 | M: Monad[F], 96 | W: Monoid[W] 97 | ): MonadIO[WriterT[F, W, ?], E] = 98 | new MonadIO[WriterT[F, W, ?], E] { 99 | override def liftIO[A](io: IO[E, A])(implicit M1: Monad[WriterT[F, W, ?]]): WriterT[F, W, A] = 100 | WriterT(F.liftIO(io).map((W.zero, _))) 101 | } 102 | 103 | implicit def eitherTMonadIO[F[_], E0, E](implicit F: MonadIO[F, E], M: Monad[F]): MonadIO[EitherT[F, E0, ?], E] = 104 | new MonadIO[EitherT[F, E0, ?], E] { 105 | override def liftIO[A](io: IO[E, A])(implicit M1: Monad[EitherT[F, E0, ?]]): EitherT[F, E0, A] = 106 | EitherT(F.liftIO(io).map(_.right[E0])) 107 | } 108 | 109 | implicit def optionTMonadIO[F[_], E](implicit F: MonadIO[F, E], M: Monad[F]): MonadIO[OptionT[F, ?], E] = 110 | new MonadIO[OptionT[F, ?], E] { 111 | override def liftIO[A](io: IO[E, A])(implicit M1: Monad[OptionT[F, ?]]): OptionT[F, A] = 112 | OptionT(F.liftIO(io).map(Some.apply)) 113 | } 114 | 115 | implicit def theseTMonadIO[F[_], E0, E](implicit F: MonadIO[F, E], M: Monad[F]): MonadIO[TheseT[F, E0, ?], E] = 116 | new MonadIO[TheseT[F, E0, ?], E] { 117 | override def liftIO[A](io: IO[E, A])(implicit M1: Monad[TheseT[F, E0, ?]]): TheseT[F, E0, A] = 118 | TheseT(F.liftIO(io).map(\&/.That.apply)) 119 | } 120 | 121 | } 122 | 123 | private[ioeffect] sealed abstract class MonadIOInstances1 { 124 | 125 | implicit def ioMonadIO[E]: MonadIO[IO[E, ?], E] = new MonadIO[IO[E, ?], E] { 126 | override def liftIO[A](io: IO[E, A])(implicit M: Monad[IO[E, ?]]): IO[E, A] = io 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/Promise.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2018 John A. De Goes. All rights reserved. 2 | 3 | package scalaz.ioeffect 4 | 5 | import java.util.concurrent.atomic.AtomicReference 6 | 7 | import Promise.internal._ 8 | 9 | /** 10 | * A promise represents an asynchronous variable that can be set exactly once, 11 | * with the ability for an arbitrary number of fibers to suspend (by calling 12 | * `get`) and automatically resume when the variable is set. 13 | * 14 | * Promises can be used for building primitive actions whose completions 15 | * require the coordinated action of multiple fibers, and for building 16 | * higher-level concurrent or asynchronous structures. 17 | * {{{ 18 | * for { 19 | * promise <- Promise.make[Void, Int] 20 | * _ <- IO.sleep(1.second).promise.complete(42).fork 21 | * value <- promise.get // Resumes when forked fiber completes promise 22 | * } yield value 23 | * }}} 24 | */ 25 | final class Promise[E, A] private (private val state: AtomicReference[State[E, A]]) extends AnyVal { 26 | 27 | /** 28 | * Retrieves the value of the promise, suspending the fiber running the action 29 | * until the result is available. 30 | */ 31 | final def get: IO[E, A] = 32 | IO.async0[E, A](k => { 33 | var result: Async[E, A] = null.asInstanceOf[Async[E, A]] 34 | var retry = true 35 | 36 | while (retry) { 37 | val oldState = state.get 38 | 39 | val newState = oldState match { 40 | case Pending(joiners) => 41 | result = Async.maybeLater[E, A](interruptJoiner(k)) 42 | 43 | Pending(k :: joiners) 44 | case s @ Done(value) => 45 | result = Async.now[E, A](value) 46 | 47 | s 48 | } 49 | 50 | retry = !state.compareAndSet(oldState, newState) 51 | } 52 | 53 | result 54 | }) 55 | 56 | /** 57 | * Completes the promise with the specified value. 58 | */ 59 | final def complete[E2](a: A): IO[E2, Boolean] = 60 | done(ExitResult.Completed[E, A](a)) 61 | 62 | /** 63 | * Fails the promise with the specified error, which will be propagated to all 64 | * fibers waiting on the value of the promise. 65 | */ 66 | final def error[E2](e: E): IO[E2, Boolean] = done(ExitResult.Failed[E, A](e)) 67 | 68 | /** 69 | * Interrupts the promise with the specified throwable. This will interrupt 70 | * all fibers waiting on the value of the promise. 71 | */ 72 | final def interrupt[E2](t: Throwable): IO[E2, Boolean] = 73 | done(ExitResult.Terminated[E, A](t)) 74 | 75 | /** 76 | * Completes the promise with the specified result. If the specified promise 77 | * has already been completed, the method will produce false. 78 | */ 79 | final def done[E2](r: ExitResult[E, A]): IO[E2, Boolean] = 80 | IO.flatten(IO.sync { 81 | var action: IO[E2, Boolean] = null.asInstanceOf[IO[E2, Boolean]] 82 | var retry = true 83 | 84 | while (retry) { 85 | val oldState = state.get 86 | 87 | val newState = oldState match { 88 | case Pending(joiners) => 89 | action = 90 | IO.forkAll(joiners.map(k => IO.sync[E2, Unit](k(r)))) *> 91 | IO.now[E2, Boolean](true) 92 | 93 | Done(r) 94 | 95 | case Done(_) => 96 | action = IO.now[E2, Boolean](false) 97 | 98 | oldState 99 | } 100 | 101 | retry = !state.compareAndSet(oldState, newState) 102 | } 103 | 104 | action 105 | }) 106 | 107 | private def interruptJoiner( 108 | joiner: ExitResult[E, A] => Unit 109 | ): Throwable => Unit = (t: Throwable) => { 110 | var retry = true 111 | 112 | while (retry) { 113 | val oldState = state.get 114 | 115 | val newState = oldState match { 116 | case Pending(joiners) => 117 | Pending(joiners.filter(j => !j.eq(joiner))) 118 | 119 | case Done(_) => 120 | oldState 121 | } 122 | 123 | retry = !state.compareAndSet(oldState, newState) 124 | } 125 | } 126 | } 127 | object Promise { 128 | 129 | /** 130 | * Makes a new promise. 131 | */ 132 | final def make[E, A]: IO[E, Promise[E, A]] = make0[E, E, A] 133 | 134 | /** 135 | * Makes a new promise. This is a more powerful variant that can utilize 136 | * different error parameters for the returned promise and the creation of the 137 | * promise. 138 | */ 139 | final def make0[E1, E2, A]: IO[E1, Promise[E2, A]] = 140 | IO.sync[E1, Promise[E2, A]] { 141 | new Promise[E2, A]( 142 | new AtomicReference[State[E2, A]](new internal.Pending[E2, A](Nil)) 143 | ) 144 | } 145 | 146 | private[ioeffect] object internal { 147 | sealed trait State[E, A] 148 | final case class Pending[E, A](joiners: List[ExitResult[E, A] => Unit]) extends State[E, A] 149 | final case class Done[E, A](value: ExitResult[E, A]) extends State[E, A] 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/RTS.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | import java.util.concurrent.atomic.AtomicReference 5 | import java.util.concurrent.{ Executors, TimeUnit } 6 | 7 | import scalaz.{ -\/, \/, \/- } 8 | 9 | import scala.annotation.{ switch, tailrec } 10 | import scala.concurrent.duration.Duration 11 | import java.util.concurrent.{ ExecutorService, ScheduledExecutorService } 12 | 13 | import scala.concurrent 14 | 15 | /** 16 | * This trait provides a high-performance implementation of a runtime system for 17 | * the `IO` monad on the JVM. 18 | */ 19 | trait RTS { 20 | import RTS._ 21 | 22 | /** 23 | * Effectfully and synchronously interprets an `IO[E, A]`, either throwing an 24 | * error, running forever, or producing an `A`. 25 | */ 26 | final def unsafePerformIO[E, A](io: IO[E, A]): A = 27 | tryUnsafePerformIO(io) match { 28 | case ExitResult.Completed(v) => v 29 | case ExitResult.Terminated(t) => throw t 30 | case ExitResult.Failed(e) => throw Errors.UnhandledError(e) 31 | } 32 | 33 | final def unsafePerformIOAsync[E, A]( 34 | io: IO[E, A] 35 | )(k: ExitResult[E, A] => Unit): Unit = { 36 | val context = new FiberContext[E, A](this, defaultHandler) 37 | 38 | context.evaluate(io) 39 | 40 | context.register(k) match { 41 | case Async.Now(v) => k(v) 42 | case _ => 43 | } 44 | } 45 | 46 | /** 47 | * WARNING: UNSAFE - convert between IO[?, A] and Future - do not use this method. 48 | * Seriously, try not to. 49 | */ 50 | final def unsafeToFuture[E, A](io: IO[E, A]): concurrent.Future[E \/ A] = { 51 | val p = concurrent.Promise[E \/ A] // we must store the information associated with E in \/ 52 | unsafePerformIOAsync(io.attempt[Throwable])({ 53 | case ExitResult.Completed(a) => val _ = p.success(a) 54 | case ExitResult.Failed(e) => val _ = p.failure(e) 55 | case ExitResult.Terminated(t) => val _ = p.failure(t) 56 | }) 57 | p.future 58 | } 59 | 60 | /** 61 | * WARNING: UNSAFE, see comments on `unsafeToFuture`. 62 | * Customised to `Task`. 63 | */ 64 | final def unsafeTaskToFuture[A](io: Task[A]): concurrent.Future[A] = { 65 | val p = concurrent.Promise[A] 66 | unsafePerformIOAsync(io)({ 67 | case ExitResult.Completed(a) => val _ = p.success(a) 68 | case ExitResult.Failed(e) => val _ = p.failure(e) 69 | case ExitResult.Terminated(t) => val _ = p.failure(t) 70 | }) 71 | p.future 72 | } 73 | 74 | /** 75 | * Effectfully interprets an `IO`, blocking if necessary to obtain the result. 76 | */ 77 | final def tryUnsafePerformIO[E, A](io: IO[E, A]): ExitResult[E, A] = { 78 | val result = new AtomicReference[ExitResult[E, A]](null) 79 | 80 | val context = new FiberContext[E, A](this, defaultHandler) 81 | 82 | context.evaluate(io) 83 | 84 | (context.register { (r: ExitResult[E, A]) => 85 | result.synchronized { 86 | result.set(r) 87 | 88 | result.notifyAll() 89 | } 90 | }) match { 91 | case Async.Now(v) => 92 | result.set(v) 93 | 94 | case _ => 95 | while (result.get eq null) { 96 | result.synchronized { 97 | if (result.get eq null) result.wait() 98 | } 99 | } 100 | } 101 | 102 | result.get 103 | } 104 | 105 | final def unsafeShutdownAndWait(timeout: Duration): Unit = { 106 | scheduledExecutor.shutdown() 107 | scheduledExecutor.awaitTermination(timeout.toMillis, TimeUnit.MILLISECONDS) 108 | threadPool.shutdown() 109 | threadPool.awaitTermination(timeout.toMillis, TimeUnit.MILLISECONDS) 110 | () 111 | } 112 | 113 | /** 114 | * The default handler for unhandled exceptions in the main fiber, and any 115 | * fibers it forks that recursively inherit the handler. 116 | */ 117 | def defaultHandler[E]: Throwable => IO[E, Unit] = 118 | (t: Throwable) => IO.sync(t.printStackTrace()) 119 | 120 | /** 121 | * The main thread pool used for executing fibers. 122 | */ 123 | val threadPool: ExecutorService = Executors.newFixedThreadPool( 124 | Runtime.getRuntime().availableProcessors().max(2) 125 | ) 126 | 127 | /** 128 | * This determines the maximum number of resumptions placed on the stack 129 | * before a fiber is shifted over to a new thread to prevent stack overflow. 130 | */ 131 | val MaxResumptionDepth = 10 132 | 133 | /** 134 | * Determines the maximum number of operations executed by a fiber before 135 | * yielding to other fibers. 136 | * 137 | * FIXME: Replace this entirely with the new scheme. 138 | */ 139 | final val YieldMaxOpCount = 1048576 140 | 141 | lazy val scheduledExecutor: ScheduledExecutorService = 142 | Executors.newScheduledThreadPool(1) 143 | 144 | final def submit[A](block: => A): Unit = { 145 | threadPool.submit(new Runnable { 146 | def run: Unit = { block; () } 147 | }) 148 | 149 | () 150 | } 151 | 152 | final def schedule[E, A](block: => A, duration: Duration): Async[E, Unit] = 153 | if (duration == Duration.Zero) { 154 | submit(block) 155 | 156 | Async.later[E, Unit] 157 | } else { 158 | val future = scheduledExecutor.schedule(new Runnable { 159 | def run: Unit = submit(block) 160 | }, duration.toNanos, TimeUnit.NANOSECONDS) 161 | 162 | Async.maybeLater { (t: Throwable) => 163 | future.cancel(true); () 164 | } 165 | } 166 | 167 | final def impureCanceler(canceler: PureCanceler): Canceler = 168 | th => unsafePerformIO(canceler(th)) 169 | } 170 | 171 | private object RTS { 172 | // Utility function to avoid catching truly fatal exceptions. Do not allocate 173 | // memory here since this would defeat the point of checking for OOME. 174 | def nonFatal(t: Throwable): Boolean = 175 | !t.isInstanceOf[InternalError] && !t.isInstanceOf[OutOfMemoryError] 176 | 177 | sealed trait RaceState 178 | object RaceState { 179 | case object Started extends RaceState 180 | case object Finished extends RaceState 181 | } 182 | 183 | type Callback[E, A] = ExitResult[E, A] => Unit 184 | 185 | @inline 186 | final def nextInstr[E](value: Any, stack: Stack): IO[E, Any] = 187 | if (!stack.isEmpty) stack.pop()(value).asInstanceOf[IO[E, Any]] else null 188 | 189 | object Catcher extends Function[Any, IO[Any, Any]] { 190 | final def apply(v: Any): IO[Any, Any] = IO.now(\/-(v)) 191 | } 192 | 193 | object IdentityCont extends Function[Any, IO[Any, Any]] { 194 | final def apply(v: Any): IO[Any, Any] = IO.now(v) 195 | } 196 | 197 | final case class Finalizer[E](finalizer: ExitResult[E, Any] => IO[Void, Unit]) extends Function[Any, IO[E, Any]] { 198 | final def apply(v: Any): IO[E, Any] = IO.now(v) 199 | } 200 | 201 | final class Stack() { 202 | type Cont = Any => IO[_, Any] 203 | 204 | private[this] var array = new Array[AnyRef](13) 205 | private[this] var size = 0 206 | private[this] var nesting = 0 207 | 208 | def isEmpty: Boolean = size == 0 209 | 210 | def push(a: Cont): Unit = 211 | if (size == 13) { 212 | array = Array(array, a, null, null, null, null, null, null, null, null, null, null, null) 213 | size = 2 214 | nesting += 1 215 | } else { 216 | array(size) = a 217 | size += 1 218 | } 219 | 220 | def pop(): Cont = { 221 | val idx = size - 1 222 | var a = array(idx) 223 | if (idx == 0 && nesting > 0) { 224 | array = a.asInstanceOf[Array[AnyRef]] 225 | a = array(12) 226 | array(12) = null // GC 227 | size = 12 228 | nesting -= 1 229 | } else { 230 | array(idx) = null // GC 231 | size = idx 232 | } 233 | a.asInstanceOf[Cont] 234 | } 235 | } 236 | 237 | /** 238 | * An implementation of Fiber that maintains context necessary for evaluation. 239 | */ 240 | final class FiberContext[E, A](rts: RTS, val unhandled: Throwable => IO[Void, Unit]) extends Fiber[E, A] { 241 | import java.util.{ Collections, Set, WeakHashMap } 242 | 243 | import FiberStatus._ 244 | import rts.{ MaxResumptionDepth, YieldMaxOpCount } 245 | 246 | // Accessed from multiple threads: 247 | private[this] val status = 248 | new AtomicReference[FiberStatus[E, A]](FiberStatus.Initial[E, A]) 249 | //@volatile 250 | private[this] var killed = false 251 | 252 | // TODO: A lot can be pulled out of status to increase performance 253 | // Also the size of this structure should be minimized with laziness used 254 | // to optimize further, to make forking a cheaper operation. 255 | 256 | // Accessed from within a single thread (not necessarily the same): 257 | private[this] var noInterrupt = 0 258 | private[this] var supervised: List[Set[FiberContext[_, _]]] = Nil 259 | private[this] var supervising = 0 260 | 261 | private[this] val stack: Stack = new Stack() 262 | 263 | final def collectDefect[E2, A2](e: ExitResult[E2, A2]): List[Throwable] = 264 | e match { 265 | case ExitResult.Terminated(t) => t :: Nil 266 | case ExitResult.Completed(_) => Nil 267 | case ExitResult.Failed(_) => Nil 268 | } 269 | 270 | /** 271 | * Creates an action to dispatch a list of errors to the fiber's uncaught 272 | * error handler. 273 | * 274 | * @param errors The effectfully produced list of errors, in reverse order. 275 | */ 276 | final def dispatchErrors( 277 | errors: IO[Void, List[Throwable]] 278 | ): IO[Void, Unit] = 279 | errors.flatMap( 280 | // Each error produced by a finalizer must be handled using the 281 | // context's unhandled exception handler: 282 | _.reverse.map(unhandled).foldLeft(IO.unit[Void])(_ *> _) 283 | ) 284 | 285 | /** 286 | * Catches an exception, returning a (possibly null) finalizer action that 287 | * must be executed. It is painstakingly *guaranteed* that the stack will be 288 | * empty in the sole case the exception was not caught by any exception 289 | * handler—i.e. the exceptional case. 290 | * 291 | * @param err The exception that is being thrown. 292 | */ 293 | final def catchError[E2](err: E): IO[E2, List[Throwable]] = { 294 | var finalizer: IO[E2, List[Throwable]] = null 295 | var body: ExitResult[E, Any] = null 296 | 297 | var caught = false 298 | 299 | // Unwind the stack, looking for exception handlers and coalescing 300 | // finalizers. 301 | while (!caught && !stack.isEmpty) { 302 | stack.pop() match { 303 | case `Catcher` => caught = true 304 | case f0: Finalizer[_] => 305 | val f = f0.asInstanceOf[Finalizer[E]] 306 | 307 | // Lazy initialization of body: 308 | if (body eq null) body = ExitResult.Failed(err) 309 | 310 | val currentFinalizer: IO[E2, List[Throwable]] = 311 | f.finalizer(body).run.map(collectDefect) 312 | 313 | if (finalizer eq null) finalizer = currentFinalizer 314 | else finalizer = finalizer.zipWith(currentFinalizer)(_ ++ _) 315 | case _ => 316 | } 317 | } 318 | 319 | // This may seem strange but we need to maintain the invariant that an 320 | // empty stack means the exception was *not* caught. So if the exception 321 | // was caught but the stack is empty, we make the stack non-empty. This 322 | // lets us return only the finalizer, which will be null for common cases, 323 | // and result in zero heap allocations for the happy path. 324 | if (caught && stack.isEmpty) stack.push(IdentityCont) 325 | 326 | finalizer 327 | } 328 | 329 | /** 330 | * Empties the stack, collecting all finalizers and coalescing them into an 331 | * action that produces a list (possibly empty) of errors during finalization. 332 | * 333 | * @param error The error being used to interrupt the fiber. 334 | */ 335 | final def interruptStack[E2](error: Throwable): IO[E2, List[Throwable]] = { 336 | // Use null to achieve zero allocs for the common case of no finalizers: 337 | var finalizer: IO[E2, List[Throwable]] = null 338 | 339 | if (!stack.isEmpty) { 340 | // Any finalizers will require ExitResult. Here we fake lazy evaluation 341 | // to eliminate unnecessary allocation: 342 | var body: ExitResult[E, Any] = null 343 | 344 | while (!stack.isEmpty) { 345 | // Peel off all the finalizers, composing them into a single finalizer 346 | // that produces a possibly empty list of errors that occurred when 347 | // executing the finalizers. The order of errors is outer-to-inner 348 | // (reverse chronological). 349 | stack.pop() match { 350 | case f0: Finalizer[_] => 351 | val f = f0.asInstanceOf[Finalizer[E]] 352 | 353 | // Lazy initialization of body: 354 | if (body eq null) body = ExitResult.Terminated(error) 355 | 356 | val currentFinalizer = 357 | f.finalizer(body).run[E2].map(collectDefect) 358 | 359 | if (finalizer eq null) finalizer = currentFinalizer 360 | else finalizer = finalizer.zipWith(currentFinalizer)(_ ++ _) 361 | case _ => 362 | } 363 | } 364 | } 365 | 366 | finalizer 367 | } 368 | 369 | /** 370 | * The main interpreter loop for `IO` actions. For purely synchronous actions, 371 | * this will run to completion unless required to yield to other fibers. 372 | * For mixed actions, the loop will proceed no further than the first 373 | * asynchronous boundary. 374 | * 375 | * @param io0 The `IO` to evaluate on the fiber. 376 | */ 377 | final def evaluate(io0: IO[E, _]): Unit = { 378 | // Do NOT accidentally capture any of local variables in a closure, 379 | // or Scala will wrap them in ObjectRef and performance will plummet. 380 | var curIo: IO[E, Any] = io0.asInstanceOf[IO[E, Any]] 381 | 382 | while (curIo ne null) { 383 | try { 384 | // Put the maximum operation count on the stack for fast access: 385 | val maxopcount = YieldMaxOpCount 386 | 387 | var result: ExitResult[E, Any] = null 388 | var eval: Boolean = true 389 | var opcount: Int = 0 390 | 391 | do { 392 | // Check to see if the fiber should continue executing or not: 393 | val die = shouldDie 394 | 395 | if (die.eq(None)) { 396 | // Fiber does not need to be interrupted, but might need to yield: 397 | if (opcount == maxopcount) { 398 | // Cooperatively yield to other fibers currently suspended. 399 | // FIXME: Replace with the new design. 400 | eval = false 401 | 402 | opcount = 0 403 | 404 | // Cannot capture `curIo` since it will be boxed into `ObjectRef`, 405 | // which destroys performance, so we create a temp val here. 406 | val tmpIo = curIo 407 | 408 | rts.submit(evaluate(tmpIo)) 409 | } else { 410 | // Fiber is neither being interrupted nor needs to yield. Execute 411 | // the next instruction in the program: 412 | (curIo.tag: @switch) match { 413 | case IO.Tags.FlatMap => 414 | val io = curIo.asInstanceOf[IO.FlatMap[E, Any, Any]] 415 | 416 | val nested = io.io 417 | 418 | // A mini interpreter for the left side of FlatMap that evaluates 419 | // anything that is 1-hop away. This eliminates heap usage for the 420 | // happy path. 421 | (nested.tag: @switch) match { 422 | case IO.Tags.Point => 423 | val io2 = nested.asInstanceOf[IO.Point[E, Any]] 424 | 425 | curIo = io.flatMapper(io2.value()) 426 | 427 | case IO.Tags.Strict => 428 | val io2 = nested.asInstanceOf[IO.Strict[E, Any]] 429 | 430 | curIo = io.flatMapper(io2.value) 431 | 432 | case IO.Tags.SyncEffect => 433 | val io2 = nested.asInstanceOf[IO.SyncEffect[E, Any]] 434 | 435 | curIo = io.flatMapper(io2.effect()) 436 | 437 | case _ => 438 | // Fallback case. We couldn't evaluate the LHS so we have to 439 | // use the stack: 440 | curIo = nested 441 | 442 | stack.push(io.flatMapper) 443 | } 444 | 445 | case IO.Tags.Point => 446 | val io = curIo.asInstanceOf[IO.Point[E, Any]] 447 | 448 | val value = io.value() 449 | 450 | curIo = nextInstr[E](value, stack) 451 | 452 | if (curIo eq null) { 453 | eval = false 454 | result = ExitResult.Completed(value) 455 | } 456 | 457 | case IO.Tags.Strict => 458 | val io = curIo.asInstanceOf[IO.Strict[E, Any]] 459 | 460 | val value = io.value 461 | 462 | curIo = nextInstr[E](value, stack) 463 | 464 | if (curIo eq null) { 465 | eval = false 466 | result = ExitResult.Completed(value) 467 | } 468 | 469 | case IO.Tags.SyncEffect => 470 | val io = curIo.asInstanceOf[IO.SyncEffect[E, Any]] 471 | 472 | val value = io.effect() 473 | 474 | curIo = nextInstr[E](value, stack) 475 | 476 | if (curIo eq null) { 477 | eval = false 478 | result = ExitResult.Completed(value) 479 | } 480 | 481 | case IO.Tags.Fail => 482 | val io = curIo.asInstanceOf[IO.Fail[E, Any]] 483 | 484 | val error = io.error 485 | 486 | val finalizer = catchError[Void](error) 487 | 488 | if (stack.isEmpty) { 489 | // Error not caught, stack is empty: 490 | if (finalizer eq null) { 491 | // No finalizer, so immediately produce the error. 492 | eval = false 493 | result = ExitResult.Failed(error) 494 | 495 | // Report the uncaught error to the supervisor: 496 | rts.submit( 497 | rts.unsafePerformIO( 498 | unhandled(Errors.UnhandledError(error)) 499 | ) 500 | ) 501 | } else { 502 | // We have finalizers to run. We'll resume executing with the 503 | // uncaught failure after we have executed all the finalizers: 504 | val finalization = dispatchErrors(finalizer) 505 | val completer = io 506 | 507 | // Do not interrupt finalization: 508 | this.noInterrupt += 1 509 | 510 | curIo = ensuringUninterruptibleExit( 511 | finalization[E] *> completer 512 | ) 513 | } 514 | } else { 515 | // Error caught: 516 | val value = -\/(error) 517 | 518 | if (finalizer eq null) { 519 | // No finalizer to run: 520 | curIo = nextInstr[E](value, stack) 521 | 522 | if (curIo eq null) { 523 | eval = false 524 | result = ExitResult.Completed(value) 525 | } 526 | } else { 527 | // Must run finalizer first: 528 | val finalization = dispatchErrors(finalizer) 529 | val completer = IO.now[E, Any](value) 530 | 531 | // Do not interrupt finalization: 532 | this.noInterrupt += 1 533 | 534 | curIo = ensuringUninterruptibleExit( 535 | finalization[E] *> completer 536 | ) 537 | } 538 | } 539 | 540 | case IO.Tags.AsyncEffect => 541 | val io = curIo.asInstanceOf[IO.AsyncEffect[E, Any]] 542 | 543 | val id = enterAsyncStart() 544 | 545 | try { 546 | io.register(resumeAsync) match { 547 | case Async.Now(value) => 548 | // Value returned synchronously, callback will never be 549 | // invoked. Attempt resumption now: 550 | if (shouldResumeAsync()) { 551 | value match { 552 | case ExitResult.Completed(v) => 553 | curIo = nextInstr[E](v, stack) 554 | 555 | if (curIo eq null) { 556 | eval = false 557 | result = value 558 | } 559 | case ExitResult.Terminated(t) => 560 | curIo = IO.terminate(t) 561 | case ExitResult.Failed(e) => 562 | curIo = IO.fail(e) 563 | } 564 | } else { 565 | // Completion handled by interruptor: 566 | eval = false 567 | } 568 | 569 | case Async.MaybeLater(canceler) => 570 | // We have a canceler, attempt to store a reference to 571 | // it in case the async computation is interrupted: 572 | awaitAsync(id, canceler) 573 | 574 | eval = false 575 | 576 | case Async.MaybeLaterIO(pureCanceler) => 577 | // As for the case above this stores an impure canceler 578 | // obtained performing the pure canceler on the same thread 579 | awaitAsync(id, rts.impureCanceler(pureCanceler)) 580 | 581 | eval = false 582 | } 583 | } finally enterAsyncEnd() 584 | 585 | case IO.Tags.AsyncIOEffect => 586 | val io = curIo.asInstanceOf[IO.AsyncIOEffect[E, Any]] 587 | 588 | enterAsyncStart() 589 | 590 | try { 591 | val value = 592 | rts.tryUnsafePerformIO(io.register(resumeAsync)) 593 | 594 | // Value returned synchronously, callback will never be 595 | // invoked. Attempt resumption now: 596 | if (shouldResumeAsync()) { 597 | value match { 598 | case ExitResult.Completed(v) => 599 | curIo = nextInstr[E](v, stack) 600 | 601 | if (curIo eq null) { 602 | eval = false 603 | result = value.asInstanceOf[ExitResult[E, Any]] 604 | } 605 | case ExitResult.Terminated(t) => 606 | curIo = IO.terminate(t) 607 | case ExitResult.Failed(e) => 608 | curIo = IO.fail(e) 609 | } 610 | } else { 611 | // Completion handled by interruptor: 612 | eval = false 613 | } 614 | } finally enterAsyncEnd() 615 | 616 | case IO.Tags.Attempt => 617 | val io = curIo.asInstanceOf[IO.Attempt[E, Any, Any]] 618 | 619 | curIo = io.value 620 | 621 | stack.push(Catcher) 622 | 623 | case IO.Tags.Fork => 624 | val io = curIo.asInstanceOf[IO.Fork[_, E, Any]] 625 | 626 | val optHandler = io.handler.toOption 627 | 628 | val handler = 629 | if (optHandler.eq(None)) unhandled else optHandler.get 630 | 631 | val value: FiberContext[_, Any] = fork(io.value, handler) 632 | 633 | supervise(value) 634 | 635 | curIo = nextInstr[E](value, stack) 636 | 637 | if (curIo eq null) { 638 | eval = false 639 | result = ExitResult.Completed(value) 640 | } 641 | 642 | case IO.Tags.Race => 643 | val io = curIo.asInstanceOf[IO.Race[E, Any, Any, Any]] 644 | 645 | curIo = raceWith(unhandled, io.left, io.right, io.finishLeft, io.finishRight) 646 | 647 | case IO.Tags.Suspend => 648 | val io = curIo.asInstanceOf[IO.Suspend[E, Any]] 649 | 650 | curIo = io.value() 651 | 652 | case IO.Tags.Bracket => 653 | val io = curIo.asInstanceOf[IO.Bracket[E, Any, Any]] 654 | 655 | val ref = new AtomicReference[Any]() 656 | 657 | val finalizer = 658 | Finalizer[E](rez => IO.suspend(if (null != ref.get) io.release(rez, ref.get) else IO.unit)) 659 | 660 | stack.push(finalizer) 661 | 662 | // TODO: This is very heavyweight and could benefit from 663 | // optimization. 664 | curIo = for { 665 | a <- (for { 666 | a <- io.acquire 667 | _ <- IO.sync(ref.set(a)) 668 | } yield a).uninterruptibly 669 | b <- io.use(a) 670 | _ <- (io.release(ExitResult.Completed(b), a)[E] <* IO 671 | .sync(ref.set(null))).uninterruptibly 672 | } yield b 673 | 674 | case IO.Tags.Uninterruptible => 675 | val io = curIo.asInstanceOf[IO.Uninterruptible[E, Any]] 676 | 677 | curIo = for { 678 | _ <- enterUninterruptible 679 | v <- io.io 680 | _ <- exitUninterruptible 681 | } yield v 682 | 683 | case IO.Tags.Sleep => 684 | val io = curIo.asInstanceOf[IO.Sleep[E]] 685 | 686 | curIo = IO.async0 { callback => 687 | rts 688 | .schedule( 689 | callback( 690 | SuccessUnit[E].asInstanceOf[ExitResult[E, Any]] 691 | ), 692 | io.duration 693 | ) 694 | .asInstanceOf[Async[E, Any]] 695 | } 696 | 697 | case IO.Tags.Supervise => 698 | val io = curIo.asInstanceOf[IO.Supervise[E, Any]] 699 | 700 | curIo = enterSupervision *> 701 | io.value.ensuring(exitSupervision(io.error)) 702 | 703 | case IO.Tags.Terminate => 704 | val io = curIo.asInstanceOf[IO.Terminate[E, Any]] 705 | 706 | val cause = io.cause 707 | 708 | val finalizer = interruptStack[Void](cause) 709 | 710 | if (finalizer eq null) { 711 | // No finalizers, simply produce error: 712 | eval = false 713 | result = ExitResult.Terminated(cause) 714 | 715 | // Report the termination cause to the supervisor: 716 | rts.submit(rts.unsafePerformIO(unhandled(cause))) 717 | } else { 718 | // Must run finalizers first before failing: 719 | val finalization = dispatchErrors(finalizer) 720 | val completer = io 721 | 722 | // Do not interrupt finalization: 723 | this.noInterrupt += 1 724 | 725 | curIo = ensuringUninterruptibleExit( 726 | finalization[E] *> completer 727 | ) 728 | } 729 | 730 | case IO.Tags.Supervisor => 731 | val value = unhandled 732 | 733 | curIo = nextInstr[E](value, stack) 734 | 735 | if (curIo eq null) { 736 | eval = false 737 | result = ExitResult.Completed(value) 738 | } 739 | 740 | case IO.Tags.Run => 741 | val io = curIo.asInstanceOf[IO.Run[E, _, Any]] 742 | 743 | val value: FiberContext[E, Any] = fork(io.value, unhandled) 744 | 745 | curIo = IO.async0[E, Any] { k => 746 | value.register { (v: ExitResult[E, Any]) => 747 | k(ExitResult.Completed(v)) 748 | } match { 749 | case Async.Now(v) => Async.Now(ExitResult.Completed(v)) 750 | case Async.MaybeLater(c) => Async.MaybeLater(c) 751 | case Async.MaybeLaterIO(c) => Async.MaybeLaterIO(c) 752 | } 753 | } 754 | } 755 | } 756 | } else { 757 | // Interruption cannot be interrupted: 758 | this.noInterrupt += 1 759 | 760 | curIo = IO.terminate[E, Any](die.get) 761 | } 762 | 763 | opcount = opcount + 1 764 | } while (eval) 765 | 766 | if (result ne null) { 767 | done(result.asInstanceOf[ExitResult[E, A]]) 768 | } 769 | 770 | curIo = null // Ensure termination of outer loop 771 | } catch { 772 | // Catastrophic error handler. Any error thrown inside the interpreter is 773 | // either a bug in the interpreter or a bug in the user's code. Let the 774 | // fiber die but attempt finalization & report errors. 775 | case t: Throwable if (nonFatal(t)) => 776 | // Interruption cannot be interrupted: 777 | this.noInterrupt += 1 778 | 779 | curIo = IO.terminate[E, Any](t) 780 | } 781 | } 782 | } 783 | 784 | final def fork[E1, A1]( 785 | io: IO[E1, A1], 786 | handler: Throwable => IO[Void, Unit] 787 | ): FiberContext[E1, A1] = { 788 | val context = new FiberContext[E1, A1](rts, handler) 789 | 790 | rts.submit(context.evaluate(io)) 791 | 792 | context 793 | } 794 | 795 | /** 796 | * Resumes a synchronous evaluation given the newly produced value. 797 | * 798 | * @param value The value which will be used to resume the sync evaluation. 799 | */ 800 | private final def resumeEvaluate(value: ExitResult[E, Any]): Unit = 801 | value match { 802 | case ExitResult.Completed(v) => 803 | // Async produced a value: 804 | val io = nextInstr[E](v, stack) 805 | 806 | if (io eq null) done(value.asInstanceOf[ExitResult[E, A]]) 807 | else evaluate(io) 808 | 809 | case ExitResult.Failed(t) => evaluate(IO.fail[E, Any](t)) 810 | 811 | case ExitResult.Terminated(t) => evaluate(IO.terminate[E, Any](t)) 812 | } 813 | 814 | /** 815 | * Resumes an asynchronous computation. 816 | * 817 | * @param value The value produced by the asynchronous computation. 818 | */ 819 | private final def resumeAsync(value: ExitResult[E, Any]): Unit = 820 | if (shouldResumeAsync()) { 821 | // TODO: CPS transform 822 | // Take care not to overflow the stack in cases of 'deeply' nested 823 | // asynchronous callbacks. 824 | if (this.reentrancy > MaxResumptionDepth) { 825 | rts.submit(resumeEvaluate(value)) 826 | } else resumeEvaluate(value) 827 | } 828 | 829 | private final def raceCallback[A2, B]( 830 | resume: ExitResult[E, IO[E, B]] => Unit, 831 | state: AtomicReference[RaceState], 832 | finish: A2 => IO[E, B] 833 | ): ExitResult[E, A2] => Unit = 834 | (tryA: ExitResult[E, A2]) => { 835 | import RaceState._ 836 | 837 | var loop = true 838 | var won = false 839 | 840 | while (loop) { 841 | val oldStatus = state.get 842 | 843 | val newState = oldStatus match { 844 | case Finished => 845 | won = false 846 | oldStatus 847 | case Started => 848 | won = true 849 | Finished 850 | } 851 | 852 | loop = !state.compareAndSet(oldStatus, newState) 853 | } 854 | 855 | if (won) resume(tryA.map(finish)) 856 | } 857 | 858 | private final def raceWith[A2, B, C]( 859 | unhandled: Throwable => IO[Void, Unit], 860 | leftIO: IO[E, A2], 861 | rightIO: IO[E, B], 862 | finishLeft: (A2, Fiber[E, B]) => IO[E, C], 863 | finishRight: (B, Fiber[E, A2]) => IO[E, C] 864 | ): IO[E, C] = { 865 | val left = fork(leftIO, unhandled) 866 | val right = fork(rightIO, unhandled) 867 | 868 | // TODO: Interrupt raced fibers if parent is interrupted 869 | 870 | val leftWins = (w: A2) => finishLeft(w, right) 871 | val rightWins = (w: B) => finishRight(w, left) 872 | 873 | val state = new AtomicReference[RaceState](RaceState.Started) 874 | 875 | IO.flatten(IO.async0[E, IO[E, C]] { k => 876 | var c1: Throwable => Unit = null 877 | var c2: Throwable => Unit = null 878 | 879 | left.register(raceCallback[A2, C](k, state, leftWins)) match { 880 | case Async.Now(tryA) => 881 | raceCallback[A2, C](k, state, leftWins)(tryA) 882 | case Async.MaybeLater(cancel) => 883 | c1 = cancel 884 | case Async.MaybeLaterIO(pureCancel) => 885 | c1 = rts.impureCanceler(pureCancel) 886 | } 887 | 888 | right.register(raceCallback[B, C](k, state, rightWins)) match { 889 | case Async.Now(tryA) => 890 | raceCallback[B, C](k, state, rightWins)(tryA) 891 | case Async.MaybeLater(cancel) => 892 | c2 = cancel 893 | case Async.MaybeLaterIO(pureCancel) => 894 | c2 = rts.impureCanceler(pureCancel) 895 | } 896 | 897 | val canceler = combineCancelers(c1, c2) 898 | 899 | if (canceler eq null) Async.later[E, IO[E, C]] 900 | else Async.maybeLater(canceler) 901 | }) 902 | } 903 | 904 | final def changeErrorUnit[E2](cb: Callback[E2, Unit]): Callback[E, Unit] = 905 | changeError[E2, E, Unit](_ => SuccessUnit[E2], cb) 906 | 907 | final def interrupt[E2](t: Throwable): IO[E2, Unit] = 908 | IO.async0[E2, Unit](cb => kill0[E2](t, changeErrorUnit[E2](cb))) 909 | 910 | final def join: IO[E, A] = IO.async0(join0) 911 | 912 | final def enterSupervision: IO[E, Unit] = IO.sync { 913 | supervising += 1 914 | 915 | def newWeakSet[B]: Set[B] = 916 | Collections.newSetFromMap[B](new WeakHashMap[B, java.lang.Boolean]()) 917 | 918 | val set = newWeakSet[FiberContext[_, _]] 919 | 920 | supervised = set :: supervised 921 | } 922 | 923 | final def supervise(child: FiberContext[_, _]): Unit = 924 | if (supervising > 0) { 925 | supervised match { 926 | case Nil => 927 | case set :: _ => 928 | set.add(child) 929 | 930 | () 931 | } 932 | } 933 | 934 | @tailrec 935 | final def enterAsyncStart(): Int = { 936 | val oldStatus = status.get 937 | 938 | oldStatus match { 939 | case AsyncRegion(reentrancy, resume, cancel, joiners, killers) => 940 | val newReentrancy = reentrancy + 1 941 | 942 | if (!status.compareAndSet( 943 | oldStatus, 944 | AsyncRegion(newReentrancy, resume + 1, cancel, joiners, killers) 945 | )) 946 | enterAsyncStart() 947 | else newReentrancy 948 | 949 | case Executing(joiners, killers) => 950 | val newReentrancy = 1 951 | 952 | if (!status.compareAndSet( 953 | oldStatus, 954 | AsyncRegion(newReentrancy, 1, None, joiners, killers) 955 | )) enterAsyncStart() 956 | else newReentrancy 957 | 958 | case _ => 959 | // If this is hit, there's a bug somewhere. 960 | throw new Error("Defect: Fiber is not in executing or async state") 961 | } 962 | } 963 | 964 | final def reentrancy: Int = status.get match { 965 | case AsyncRegion(v, _, _, _, _) => v 966 | 967 | case _ => 0 968 | } 969 | 970 | @tailrec 971 | final def enterAsyncEnd(): Unit = { 972 | val oldStatus = status.get 973 | 974 | oldStatus match { 975 | case AsyncRegion(1, 0, _, joiners, killers) => 976 | // No more resumptions left and exiting last async boundary initiation: 977 | if (!status.compareAndSet(oldStatus, Executing(joiners, killers))) 978 | enterAsyncEnd() 979 | 980 | case AsyncRegion(reentrancy, resume, cancel, joiners, killers) => 981 | if (!status.compareAndSet( 982 | oldStatus, 983 | AsyncRegion(reentrancy - 1, resume, cancel, joiners, killers) 984 | )) 985 | enterAsyncEnd() 986 | 987 | case _ => 988 | } 989 | } 990 | 991 | @tailrec 992 | final def awaitAsync(id: Int, c: Throwable => Unit): Unit = { 993 | val oldStatus = status.get 994 | 995 | oldStatus match { 996 | case AsyncRegion(reentrancy, resume, _, joiners, killers) if (id == reentrancy) => 997 | if (!status.compareAndSet( 998 | oldStatus, 999 | AsyncRegion(reentrancy, resume, Some(c), joiners, killers) 1000 | )) 1001 | awaitAsync(id, c) 1002 | 1003 | case _ => 1004 | } 1005 | } 1006 | 1007 | @tailrec 1008 | final def shouldResumeAsync(): Boolean = { 1009 | val oldStatus = status.get 1010 | 1011 | oldStatus match { 1012 | case AsyncRegion(0, 1, _, joiners, killers) => 1013 | // No more resumptions are left! 1014 | if (!status.compareAndSet(oldStatus, Executing(joiners, killers))) 1015 | shouldResumeAsync() 1016 | else true 1017 | 1018 | case AsyncRegion(reentrancy, resume, _, joiners, killers) => 1019 | if (!status.compareAndSet( 1020 | oldStatus, 1021 | AsyncRegion(reentrancy, resume - 1, None, joiners, killers) 1022 | )) 1023 | shouldResumeAsync() 1024 | else true 1025 | 1026 | case _ => false 1027 | } 1028 | } 1029 | 1030 | final def exitSupervision[E2](e: Throwable): IO[E2, Unit] = 1031 | IO.flatten(IO.sync { 1032 | supervising -= 1 1033 | 1034 | var action = IO.unit[E2] 1035 | 1036 | supervised = supervised match { 1037 | case Nil => Nil 1038 | case set :: tail => 1039 | val iterator = set.iterator() 1040 | 1041 | while (iterator.hasNext()) { 1042 | val child = iterator.next() 1043 | 1044 | action = action *> child.interrupt[E2](e) 1045 | } 1046 | 1047 | tail 1048 | } 1049 | 1050 | action 1051 | }) 1052 | 1053 | @inline 1054 | final def shouldDie: Option[Throwable] = 1055 | if (!killed || noInterrupt > 0) None 1056 | else { 1057 | val oldStatus = status.get 1058 | 1059 | oldStatus match { 1060 | case Interrupting(error, _, _) => Some(error) 1061 | case _ => None 1062 | } 1063 | } 1064 | 1065 | final def ensuringUninterruptibleExit[Z](io: IO[E, Z]): IO[E, Z] = 1066 | for { 1067 | a <- io 1068 | _ <- exitUninterruptible 1069 | } yield a 1070 | 1071 | final def enterUninterruptible: IO[E, Unit] = IO.sync { noInterrupt += 1 } 1072 | 1073 | final def exitUninterruptible: IO[E, Unit] = IO.sync { noInterrupt -= 1 } 1074 | 1075 | final def register(cb: Callback[E, A]): Async[E, A] = join0(cb) 1076 | 1077 | @tailrec 1078 | final def done(v: ExitResult[E, A]): Unit = { 1079 | val oldStatus = status.get 1080 | 1081 | oldStatus match { 1082 | case Executing(joiners, killers) => 1083 | if (!status.compareAndSet(oldStatus, Done(v))) done(v) 1084 | else purgeJoinersKillers(v, joiners, killers) 1085 | 1086 | case Interrupting(_, joiners, killers) => 1087 | if (!status.compareAndSet(oldStatus, Done(v))) done(v) 1088 | else purgeJoinersKillers(v, joiners, killers) 1089 | 1090 | case AsyncRegion(_, _, _, joiners, killers) => 1091 | // TODO: Guard against errant `done` or not? 1092 | if (!status.compareAndSet(oldStatus, Done(v))) done(v) 1093 | else purgeJoinersKillers(v, joiners, killers) 1094 | 1095 | case Done(_) => // Huh? 1096 | } 1097 | } 1098 | 1099 | def changeError[E1, E2, A1](f: E2 => ExitResult[E1, A1], cb: Callback[E1, A1]): Callback[E2, A1] = { 1100 | case ExitResult.Completed(a) => cb(ExitResult.Completed(a)) 1101 | case ExitResult.Terminated(t) => cb(ExitResult.Terminated(t)) 1102 | case ExitResult.Failed(e2) => cb(f(e2)) 1103 | } 1104 | @tailrec 1105 | private final def kill0[E2](t: Throwable, cb: Callback[E, Unit]): Async[E2, Unit] = { 1106 | killed = true 1107 | 1108 | val oldStatus = status.get 1109 | 1110 | oldStatus match { 1111 | case Executing(joiners, killers) => 1112 | if (!status.compareAndSet(oldStatus, Interrupting(t, joiners, cb :: killers))) 1113 | kill0(t, cb) 1114 | else Async.later[E2, Unit] 1115 | 1116 | case Interrupting(t, joiners, killers) => 1117 | if (!status.compareAndSet(oldStatus, Interrupting(t, joiners, cb :: killers))) 1118 | kill0(t, cb) 1119 | else Async.later[E2, Unit] 1120 | 1121 | case AsyncRegion(_, resume, cancelOpt, joiners, killers) if (resume > 0 && noInterrupt == 0) => 1122 | val v = ExitResult.Terminated[E, A](t) 1123 | 1124 | if (!status.compareAndSet(oldStatus, Done(v))) kill0(t, cb) 1125 | else { 1126 | // We interrupted async before it could resume. Now we have to 1127 | // cancel the computation, if possible, and handle any finalizers. 1128 | cancelOpt match { 1129 | case None => 1130 | case Some(cancel) => 1131 | try cancel(t) 1132 | catch { 1133 | case t: Throwable if (nonFatal(t)) => 1134 | fork(unhandled(t), unhandled) 1135 | } 1136 | } 1137 | 1138 | val finalizer = interruptStack[Void](t) 1139 | 1140 | if (finalizer ne null) { 1141 | fork[Void, Unit](dispatchErrors(finalizer), unhandled) 1142 | } 1143 | 1144 | purgeJoinersKillers(v, joiners, killers) 1145 | 1146 | Async.now(SuccessUnit[E2]) 1147 | } 1148 | 1149 | case AsyncRegion(_, _, _, joiners, killers) => 1150 | if (!status.compareAndSet(oldStatus, Interrupting(t, joiners, cb :: killers))) 1151 | kill0(t, cb) 1152 | else Async.later[E2, Unit] 1153 | 1154 | case Done(_) => Async.now(SuccessUnit[E2]) 1155 | } 1156 | } 1157 | 1158 | @tailrec 1159 | private final def join0(cb: Callback[E, A]): Async[E, A] = { 1160 | val oldStatus = status.get 1161 | 1162 | oldStatus match { 1163 | case Executing(joiners, killers) => 1164 | if (!status.compareAndSet(oldStatus, Executing(cb :: joiners, killers))) 1165 | join0(cb) 1166 | else Async.later[E, A] 1167 | 1168 | case Interrupting(t, joiners, killers) => 1169 | if (!status.compareAndSet(oldStatus, Interrupting(t, cb :: joiners, killers))) 1170 | join0(cb) 1171 | else Async.later[E, A] 1172 | 1173 | case AsyncRegion(reenter, resume, cancel, joiners, killers) => 1174 | if (!status.compareAndSet( 1175 | oldStatus, 1176 | AsyncRegion(reenter, resume, cancel, cb :: joiners, killers) 1177 | )) join0(cb) 1178 | else Async.later[E, A] 1179 | 1180 | case Done(v) => Async.now(v) 1181 | } 1182 | } 1183 | 1184 | private final def purgeJoinersKillers( 1185 | v: ExitResult[E, A], 1186 | joiners: List[Callback[E, A]], 1187 | killers: List[Callback[E, Unit]] 1188 | ): Unit = { 1189 | // To preserve fair scheduling, we submit all resumptions on the thread 1190 | // pool in (rough) order of their submission. 1191 | killers.reverse.foreach(k => rts.submit(k(SuccessUnit[E]))) 1192 | joiners.foreach(k => rts.submit(k(v))) 1193 | } 1194 | } 1195 | 1196 | sealed trait FiberStatus[E, A] 1197 | object FiberStatus { 1198 | final case class Executing[E, A](joiners: List[Callback[E, A]], killers: List[Callback[E, Unit]]) 1199 | extends FiberStatus[E, A] 1200 | final case class Interrupting[E, A]( 1201 | error: Throwable, 1202 | joiners: List[Callback[E, A]], 1203 | killers: List[Callback[E, Unit]] 1204 | ) extends FiberStatus[E, A] 1205 | final case class AsyncRegion[E, A]( 1206 | reentrancy: Int, 1207 | resume: Int, 1208 | cancel: Option[Throwable => Unit], 1209 | joiners: List[Callback[E, A]], 1210 | killers: List[Callback[E, Unit]] 1211 | ) extends FiberStatus[E, A] 1212 | final case class Done[E, A](value: ExitResult[E, A]) extends FiberStatus[E, A] 1213 | 1214 | def Initial[E, A]: Executing[E, A] = Executing[E, A](Nil, Nil) 1215 | } 1216 | 1217 | val _SuccessUnit: ExitResult[Nothing, Unit] = ExitResult.Completed(()) 1218 | 1219 | final def SuccessUnit[E]: ExitResult[E, Unit] = 1220 | _SuccessUnit.asInstanceOf[ExitResult[E, Unit]] 1221 | 1222 | final def combineCancelers(c1: Throwable => Unit, c2: Throwable => Unit): Throwable => Unit = 1223 | if (c1 eq null) { 1224 | if (c2 eq null) null 1225 | else c2 1226 | } else if (c2 eq null) { 1227 | c1 1228 | } else 1229 | (t: Throwable) => { 1230 | c1(t) 1231 | c2(t) 1232 | } 1233 | } 1234 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/SafeApp.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | import scala.concurrent.duration.Duration 5 | 6 | /** 7 | * The entry point for a purely-functional application on the JVM. 8 | * 9 | * {{{ 10 | * import java.io.IOException 11 | * import scalaz.effect.{IO, SafeApp} 12 | * import scalaz.effect.console._ 13 | * 14 | * object MyApp extends SafeApp { 15 | * 16 | * def run(args: List[String]): IO[Void, ExitStatus] = 17 | * myAppLogic.attempt.map(_.fold(_ => 1)(_ => 0)).map(ExitStatus.ExitNow(_)) 18 | * 19 | * def myAppLogic: IO[IOException, Unit] = 20 | * for { 21 | * _ <- putStrLn("Hello! What is your name?") 22 | * n <- getStrLn 23 | * _ <- putStrLn("Hello, " + n + ", good to meet you!") 24 | * } yield () 25 | * } 26 | * }}} 27 | */ 28 | trait SafeApp extends RTS { 29 | 30 | sealed trait ExitStatus 31 | object ExitStatus { 32 | case class ExitNow(code: Int) extends ExitStatus 33 | case class ExitWhenDone(code: Int, timeout: Duration) extends ExitStatus 34 | case object DoNotExit extends ExitStatus 35 | } 36 | 37 | /** 38 | * The main function of the application, which will be passed the command-line 39 | * arguments to the program and has to return an `IO` with the errors fully handled. 40 | */ 41 | def run(args: List[String]): IO[Void, ExitStatus] 42 | 43 | /** 44 | * The Scala main function, intended to be called only by the Scala runtime. 45 | */ 46 | final def main(args0: Array[String]): Unit = 47 | unsafePerformIO(run(args0.toList)) match { 48 | case ExitStatus.ExitNow(code) => 49 | sys.exit(code) 50 | case ExitStatus.ExitWhenDone(code, timeout) => 51 | unsafeShutdownAndWait(timeout) 52 | sys.exit(code) 53 | case ExitStatus.DoNotExit => 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/Void.scala: -------------------------------------------------------------------------------- 1 | package scalaz 2 | package ioeffect 3 | 4 | import scalaz.Leibniz.=== 5 | import scalaz.Liskov.<~< 6 | 7 | trait VoidModule { 8 | 9 | trait Tag extends Any 10 | 11 | type Void <: Tag 12 | 13 | def absurd[A](v: Void): A 14 | 15 | def unsafeVoid: Void 16 | 17 | def isNothing: Void === Nothing 18 | 19 | def conforms[A]: Void <~< A 20 | } 21 | 22 | trait VoidFunctions { 23 | @inline final def absurd[A](v: Void): A = Void.absurd[A](v) 24 | } 25 | 26 | trait VoidSyntax { 27 | implicit class Ops(v: Void) { 28 | def absurd[A]: A = Void.absurd(v) 29 | } 30 | } 31 | 32 | // NOTE: this is some next level black compiler magic 33 | // but without this object syntax doesn't resolve... 34 | object VoidModule extends VoidSyntax { 35 | implicit def void_<~<[A]: Void <~< A = Void.conforms[A] 36 | } 37 | 38 | private[ioeffect] object VoidImpl extends VoidModule with VoidSyntax { 39 | type Void = Nothing 40 | 41 | def absurd[A](v: Void): A = v 42 | 43 | private[ioeffect] final class UnsafeVoid extends RuntimeException 44 | 45 | def unsafeVoid: Void = throw new UnsafeVoid 46 | 47 | def isNothing: Void === Nothing = Leibniz.refl[Void] 48 | 49 | def conforms[A]: Void <~< A = Liskov.refl[Void] 50 | 51 | } 52 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/console/package.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | import java.io.IOException 5 | 6 | package object console { 7 | private val ioException: PartialFunction[Throwable, IOException] = { 8 | case e: IOException => e 9 | } 10 | 11 | /** 12 | * Prints text to the console. 13 | */ 14 | def putStr(line: String): IO[IOException, Unit] = 15 | IO.syncCatch(scala.Console.print(line))(ioException) 16 | 17 | /** 18 | * Prints a line of text to the console, including a newline character. 19 | */ 20 | def putStrLn(line: String): IO[IOException, Unit] = 21 | IO.syncCatch(scala.Console.println(line))(ioException) 22 | 23 | /** 24 | * Retrieves a line of input from the console. 25 | */ 26 | def getStrLn: IO[IOException, String] = 27 | IO.syncCatch(scala.io.StdIn.readLine())(ioException) 28 | } 29 | -------------------------------------------------------------------------------- /ioeffect/src/main/scala/scalaz/ioeffect/package.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2018 John A. De Goes. All rights reserved. 2 | 3 | package scalaz 4 | 5 | import scala.concurrent.{ ExecutionContext, Future } 6 | import scala.concurrent.duration.Duration 7 | 8 | import scalaz.Tags.Parallel 9 | 10 | package object ioeffect { 11 | 12 | implicit class IOVoidSyntax[A](val io: IO[Void, A]) extends AnyRef { 13 | def apply[E]: IO[E, A] = io.asInstanceOf[IO[E, A]] 14 | } 15 | 16 | type Task[A] = IO[Throwable, A] 17 | object Task { 18 | type Par[a] = Task[a] @@ Parallel 19 | 20 | final def apply[A](effect: => A): Task[A] = IO.syncThrowable(effect) 21 | 22 | final def now[A](effect: A): Task[A] = IO.now(effect) 23 | final def point[A](effect: => A): Task[A] = IO.point(effect) 24 | final def sync[A](effect: => A): Task[A] = IO.sync(effect) 25 | final def async[A](register: (ExitResult[Throwable, A] => Unit) => Unit): Task[A] = IO.async(register) 26 | 27 | final def fail[A](error: Throwable): Task[A] = IO.fail(error) 28 | 29 | final def unit: Task[Unit] = IO.unit 30 | final def sleep(duration: Duration): Task[Unit] = IO.sleep(duration) 31 | 32 | final def fromFuture[E, A](io: Task[Future[A]])(ec: ExecutionContext): Task[A] = IO.fromFuture(io)(ec) 33 | } 34 | 35 | type Unexceptional[A] = IO[Void, A] 36 | 37 | type Void = Void.Void // required at this level for working Void 38 | 39 | val Void: VoidModule = VoidImpl 40 | 41 | type Canceler = Throwable => Unit 42 | type PureCanceler = Throwable => IO[Void, Unit] 43 | } 44 | -------------------------------------------------------------------------------- /ioeffect/src/test/scala/scalaz/ioeffect/IOTest.scala: -------------------------------------------------------------------------------- 1 | package scalaz.ioeffect 2 | 3 | import scala.concurrent.ExecutionContext 4 | 5 | import org.specs2.concurrent.ExecutionEnv 6 | import org.specs2.Specification 7 | import org.specs2.specification.AroundTimeout 8 | import org.specs2.matcher.MatchResult 9 | import org.specs2.specification.core.SpecStructure 10 | import scalaz._ 11 | import Scalaz._ 12 | 13 | import scala.concurrent.Future 14 | 15 | class IOTest(ee: ExecutionEnv) extends Specification with AroundTimeout with RTS { 16 | val ec: ExecutionContext = ee.ec 17 | 18 | override def defaultHandler[E]: Throwable => IO[E, Unit] = _ => IO.unit[E] 19 | 20 | def is: SpecStructure = 21 | s2""" 22 | IO `fromFuture` values with `IO.now` 23 | values should respect Future.successful $fromFutureSpecIONowSuccessful 24 | values should respect Future.apply $fromFutureSpecIONowApply 25 | 26 | IO `fromFuture` values with `IO.point` 27 | values should respect Future.successful $fromFutureSpecIONowSuccessful 28 | values should respect Future.apply $fromFutureSpecIONowApply 29 | """ 30 | 31 | def fromFutureSpecIONowSuccessful: MatchResult[Int] = 32 | unsafePerformIO(IO.fromFuture(IO.now(Future.successful(1)))(ec)) must_=== 33 | unsafePerformIO(IO.now[Throwable, Int](1)) 34 | 35 | def fromFutureSpecIONowApply: MatchResult[Int] = 36 | unsafePerformIO(IO.fromFuture(IO.now(Future(1)(ec)))(ec)) must_=== 37 | unsafePerformIO(IO.now[Throwable, Int](1)) 38 | 39 | def fromFutureSpecIOPointSuccessful: MatchResult[Int] = 40 | unsafePerformIO(IO.fromFuture(IO.point(Future.successful(1)))(ec)) must_=== 41 | unsafePerformIO(IO.now[Throwable, Int](1)) 42 | 43 | def fromFutureSpecIOPointApply: MatchResult[Int] = 44 | unsafePerformIO(IO.fromFuture(IO.point(Future(1)(ec)))(ec)) must_=== 45 | unsafePerformIO(IO.now[Throwable, Int](1)) 46 | 47 | def unsafeToFutureSpec: MatchResult[Future[Throwable \/ Int]] = 48 | unsafeToFuture(IO.now[Throwable, Int](1)).must_===(Future.successful(1.right[Throwable])) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /ioeffect/src/test/scala/scalaz/ioeffect/RTSTest.scala: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017-2018 John A. De Goes. All rights reserved. 2 | package scalaz.ioeffect 3 | 4 | import scala.concurrent.duration._ 5 | 6 | import org.specs2.concurrent.ExecutionEnv 7 | import org.specs2.Specification 8 | import org.specs2.specification.AroundTimeout 9 | import org.specs2.matcher.MatchResult 10 | import org.specs2.specification.core.SpecStructure 11 | 12 | import scalaz._ 13 | import scalaz.ioeffect.Errors.UnhandledError 14 | 15 | class RTSSpec(implicit ee: ExecutionEnv) extends Specification with AroundTimeout with RTS { 16 | 17 | override def defaultHandler[E]: Throwable => IO[E, Unit] = _ => IO.unit[E] 18 | 19 | def is: SpecStructure = 20 | s2""" 21 | RTS synchronous correctness 22 | widen Void $testWidenVoid 23 | evaluation of point $testPoint 24 | point must be lazy $testPointIsLazy 25 | now must be eager $testNowIsEager 26 | suspend must be lazy $testSuspendIsLazy 27 | suspend must be evaluatable $testSuspendIsEvaluatable 28 | point, bind, map $testSyncEvalLoop 29 | sync effect $testEvalOfSyncEffect 30 | deep effects $testEvalOfDeepSyncEffect 31 | 32 | RTS failure 33 | error in sync effect $testEvalOfAttemptOfSyncEffectError 34 | attempt . fail $testEvalOfAttemptOfFail 35 | deep attempt sync effect error $testAttemptOfDeepSyncEffectError 36 | deep attempt fail error $testAttemptOfDeepFailError 37 | uncaught fail $testEvalOfUncaughtFail 38 | uncaught sync effect error $testEvalOfUncaughtThrownSyncEffect 39 | deep uncaught sync effect error $testEvalOfDeepUncaughtThrownSyncEffect 40 | deep uncaught fail $testEvalOfDeepUncaughtFail 41 | 42 | RTS bracket 43 | fail ensuring $testEvalOfFailEnsuring 44 | fail on error $testEvalOfFailOnError 45 | finalizer errors not caught $testErrorInFinalizerCannotBeCaught 46 | finalizer errors reported ${upTo(1.second)( 47 | testErrorInFinalizerIsReported 48 | )} 49 | bracket result is usage result $testExitResultIsUsageResult 50 | error in just acquisition $testBracketErrorInAcquisition 51 | error in just release $testBracketErrorInRelease 52 | error in just usage $testBracketErrorInUsage 53 | rethrown caught error in acquisition $testBracketRethrownCaughtErrorInAcquisition 54 | rethrown caught error in release $testBracketRethrownCaughtErrorInRelease 55 | rethrown caught error in usage $testBracketRethrownCaughtErrorInUsage 56 | test eval of async fail $testEvalOfAsyncAttemptOfFail 57 | 58 | RTS synchronous stack safety 59 | deep map of point $testDeepMapOfPoint 60 | deep map of now $testDeepMapOfNow 61 | deep map of sync effect $testDeepMapOfSyncEffectIsStackSafe 62 | deep attempt $testDeepAttemptIsStackSafe 63 | deep absolve/attempt is identity $testDeepAbsolveAttemptIsIdentity 64 | deep async absolve/attempt is identity $testDeepAsyncAbsolveAttemptIsIdentity 65 | 66 | RTS asynchronous stack safety 67 | deep bind of async chain $testDeepBindOfAsyncChainIsStackSafe 68 | 69 | RTS asynchronous correctness 70 | simple async must return $testAsyncEffectReturns 71 | sleep 0 must return ${upTo(1.second)( 72 | testSleepZeroReturns 73 | )} 74 | 75 | RTS concurrency correctness 76 | shallow fork/join identity $testForkJoinIsId 77 | deep fork/join identity $testDeepForkJoinIsId 78 | interrupt of never ${upTo(1.second)( 79 | testNeverIsInterruptible 80 | )} 81 | race of value & never ${upTo(1.second)( 82 | testRaceOfValueNever 83 | )} 84 | par regression ${upTo(5.seconds)(testPar)} 85 | par of now values ${upTo(5.seconds)(testRepeatedPar)} 86 | 87 | RTS regression tests 88 | regression 1 $testDeadlockRegression 89 | 90 | """ 91 | 92 | def testPoint: MatchResult[Int] = 93 | unsafePerformIO(IO.point(1)).must_===(1) 94 | 95 | def testWidenVoid: MatchResult[String] = { 96 | val op1 = IO.sync[RuntimeException, String]("1") 97 | val op2 = IO.sync[Void, String]("2") 98 | 99 | val result: IO[RuntimeException, String] = for { 100 | r1 <- op1 101 | r2 <- op2.widenError[RuntimeException] 102 | } yield r1 + r2 103 | 104 | unsafePerformIO(result) must_=== "12" 105 | } 106 | 107 | def testPointIsLazy: MatchResult[IO[Nothing, Nothing]] = 108 | IO.point(throw new Error("Not lazy")).must(not(throwA[Throwable])) 109 | 110 | def testNowIsEager: MatchResult[IO[Nothing, Nothing]] = 111 | IO.now(throw new Error("Eager")).must(throwA[Error]) 112 | 113 | def testSuspendIsLazy: MatchResult[IO[Nothing, Nothing]] = 114 | IO.suspend(throw new Error("Eager")).must(not(throwA[Throwable])) 115 | 116 | def testSuspendIsEvaluatable: MatchResult[Int] = 117 | unsafePerformIO(IO.suspend(IO.point[Throwable, Int](42))).must_===(42) 118 | 119 | def testSyncEvalLoop: MatchResult[BigInt] = { 120 | def fibIo(n: Int): IO[Throwable, BigInt] = 121 | if (n <= 1) IO.point(n) 122 | else 123 | for { 124 | a <- fibIo(n - 1) 125 | b <- fibIo(n - 2) 126 | } yield a + b 127 | 128 | unsafePerformIO(fibIo(10)).must_===(fib(10)) 129 | } 130 | 131 | def testEvalOfSyncEffect: MatchResult[Int] = { 132 | def sumIo(n: Int): IO[Throwable, Int] = 133 | if (n <= 0) IO.sync(0) 134 | else IO.sync(n).flatMap(b => sumIo(n - 1).map(a => a + b)) 135 | 136 | unsafePerformIO(sumIo(1000)).must_===(sum(1000)) 137 | } 138 | 139 | def testEvalOfAttemptOfSyncEffectError: MatchResult[\/[Throwable, Nothing]] = 140 | unsafePerformIO(IO.syncThrowable(throw ExampleError).attempt[Throwable]) 141 | .must_===(-\/(ExampleError)) 142 | 143 | def testEvalOfAttemptOfFail: MatchResult[\/[Throwable, Int]] = { 144 | unsafePerformIO(IO.fail[Throwable, Int](ExampleError).attempt[Throwable]) 145 | .must_===(-\/(ExampleError)) 146 | 147 | unsafePerformIO( 148 | IO.suspend( 149 | IO.suspend(IO.fail[Throwable, Int](ExampleError)).attempt[Throwable] 150 | ) 151 | ).must_===( 152 | -\/( 153 | ExampleError 154 | ) 155 | ) 156 | } 157 | 158 | def testAttemptOfDeepSyncEffectError: MatchResult[\/[Throwable, Unit]] = 159 | unsafePerformIO(deepErrorEffect(100).attempt[Throwable]) 160 | .must_===(-\/(ExampleError)) 161 | 162 | def testAttemptOfDeepFailError: MatchResult[\/[Throwable, Unit]] = 163 | unsafePerformIO(deepErrorFail(100).attempt[Throwable]) 164 | .must_===(-\/(ExampleError)) 165 | 166 | def testEvalOfUncaughtFail: MatchResult[Int] = 167 | unsafePerformIO(IO.fail[Throwable, Int](ExampleError)) 168 | .must(throwA(UnhandledError(ExampleError))) 169 | 170 | def testEvalOfUncaughtThrownSyncEffect: MatchResult[Int] = 171 | unsafePerformIO(IO.sync[Throwable, Int](throw ExampleError)) 172 | .must(throwA(ExampleError)) 173 | 174 | def testEvalOfDeepUncaughtThrownSyncEffect: MatchResult[Unit] = 175 | unsafePerformIO(deepErrorEffect(100)) 176 | .must(throwA(UnhandledError(ExampleError))) 177 | 178 | def testEvalOfDeepUncaughtFail: MatchResult[Unit] = 179 | unsafePerformIO(deepErrorEffect(100)) 180 | .must(throwA(UnhandledError(ExampleError))) 181 | 182 | def testEvalOfFailEnsuring: MatchResult[Boolean] = { 183 | var finalized = false 184 | 185 | unsafePerformIO( 186 | IO.fail[Throwable, Unit](ExampleError) 187 | .ensuring(IO.sync[Void, Unit] { finalized = true; () }) 188 | ).must( 189 | throwA( 190 | UnhandledError(ExampleError) 191 | ) 192 | ) 193 | finalized.must_===(true) 194 | } 195 | 196 | def testEvalOfFailOnError: MatchResult[Boolean] = { 197 | var finalized = false 198 | 199 | unsafePerformIO( 200 | IO.fail[Throwable, Unit](ExampleError) 201 | .onError(_ => IO.sync[Void, Unit] { finalized = true; () }) 202 | ).must(throwA(UnhandledError(ExampleError))) 203 | 204 | finalized.must_===(true) 205 | } 206 | 207 | def testErrorInFinalizerCannotBeCaught: MatchResult[Int] = { 208 | val nested: IO[Throwable, Int] = 209 | IO.fail[Throwable, Int](ExampleError) 210 | .ensuring(IO.terminate(new Error("e2"))) 211 | .ensuring(IO.terminate(new Error("e3"))) 212 | 213 | unsafePerformIO(nested).must(throwA(UnhandledError(ExampleError))) 214 | } 215 | 216 | def testErrorInFinalizerIsReported: MatchResult[Int] = { 217 | var reported: Throwable = null 218 | 219 | unsafePerformIO { 220 | IO.point[Void, Int](42) 221 | .ensuring(IO.terminate(ExampleError)) 222 | .fork0(e => IO.sync[Void, Unit] { reported = e; () }) 223 | } 224 | 225 | // FIXME: Is this an issue with thread synchronization? 226 | while (reported eq null) Thread.`yield`() 227 | 228 | ((throw reported): Int).must(throwA(ExampleError)) 229 | } 230 | 231 | def testExitResultIsUsageResult: MatchResult[Int] = 232 | unsafePerformIO( 233 | IO.unit.bracket_(IO.unit[Void])(IO.point[Throwable, Int](42)) 234 | ).must_===(42) 235 | 236 | def testBracketErrorInAcquisition: MatchResult[Unit] = 237 | unsafePerformIO( 238 | IO.fail[Throwable, Unit](ExampleError).bracket_(IO.unit)(IO.unit) 239 | ).must(throwA(UnhandledError(ExampleError))) 240 | 241 | def testBracketErrorInRelease: MatchResult[Unit] = 242 | unsafePerformIO( 243 | IO.unit[Void].bracket_(IO.terminate(ExampleError))(IO.unit[Void]) 244 | ).must(throwA(ExampleError)) 245 | 246 | def testBracketErrorInUsage: MatchResult[Unit] = 247 | unsafePerformIO( 248 | IO.unit.bracket_(IO.unit)(IO.fail[Throwable, Unit](ExampleError)) 249 | ).must(throwA(UnhandledError(ExampleError))) 250 | 251 | def testBracketRethrownCaughtErrorInAcquisition: MatchResult[Unit] = { 252 | lazy val actual = unsafePerformIO( 253 | IO.unit[Void].bracket_(IO.terminate(ExampleError))(IO.unit[Void]) 254 | ) 255 | 256 | actual.must(throwA(ExampleError)) 257 | } 258 | 259 | def testBracketRethrownCaughtErrorInRelease: MatchResult[Unit] = { 260 | lazy val actual = unsafePerformIO( 261 | IO.unit[Void].bracket_(IO.terminate(ExampleError))(IO.unit[Void]) 262 | ) 263 | 264 | actual.must(throwA(ExampleError)) 265 | } 266 | 267 | def testBracketRethrownCaughtErrorInUsage: MatchResult[Unit] = { 268 | lazy val actual = unsafePerformIO( 269 | IO.absolve( 270 | IO.unit 271 | .bracket_(IO.unit)(IO.fail[Throwable, Unit](ExampleError)) 272 | .attempt[Throwable] 273 | ) 274 | ) 275 | 276 | actual.must(throwA(UnhandledError(ExampleError))) 277 | } 278 | 279 | def testEvalOfAsyncAttemptOfFail: MatchResult[Unit] = { 280 | val io1 = IO.unit.bracket_(AsyncUnit[Void])(asyncExampleError[Unit]) 281 | val io2 = AsyncUnit[Throwable].bracket_(IO.unit)(asyncExampleError[Unit]) 282 | 283 | unsafePerformIO(io1).must(throwA(UnhandledError(ExampleError))) 284 | unsafePerformIO(io2).must(throwA(UnhandledError(ExampleError))) 285 | unsafePerformIO(IO.absolve(io1.attempt[Throwable])) 286 | .must(throwA(UnhandledError(ExampleError))) 287 | unsafePerformIO(IO.absolve(io2.attempt[Throwable])) 288 | .must(throwA(UnhandledError(ExampleError))) 289 | } 290 | 291 | def testEvalOfDeepSyncEffect: MatchResult[Int] = { 292 | def incLeft(n: Int, ref: IORef[Int]): IO[Throwable, Int] = 293 | if (n <= 0) ref.read 294 | else incLeft(n - 1, ref) <* ref.modify(_ + 1) 295 | 296 | def incRight(n: Int, ref: IORef[Int]): IO[Throwable, Int] = 297 | if (n <= 0) ref.read 298 | else ref.modify(_ + 1) *> incRight(n - 1, ref) 299 | 300 | unsafePerformIO(for { 301 | ref <- IORef(0) 302 | v <- incLeft(100, ref) 303 | } yield v).must_===(100) 304 | 305 | unsafePerformIO(for { 306 | ref <- IORef(0) 307 | v <- incRight(1000, ref) 308 | } yield v).must_===(1000) 309 | } 310 | 311 | def testDeepMapOfPoint: MatchResult[Int] = 312 | unsafePerformIO(deepMapPoint(10000)).must_===(10000) 313 | 314 | def testDeepMapOfNow: MatchResult[Int] = 315 | unsafePerformIO(deepMapNow(10000)).must_===(10000) 316 | 317 | def testDeepMapOfSyncEffectIsStackSafe: MatchResult[Int] = 318 | unsafePerformIO(deepMapEffect(10000)).must_===(10000) 319 | 320 | def testDeepAttemptIsStackSafe: MatchResult[Unit] = 321 | unsafePerformIO( 322 | 0.until(10000).foldLeft(IO.sync[Throwable, Unit](())) { (acc, _) => 323 | acc.attempt[Throwable].toUnit 324 | } 325 | ).must_===(()) 326 | 327 | def testDeepAbsolveAttemptIsIdentity: MatchResult[Int] = 328 | unsafePerformIO((0 until 1000).foldLeft(IO.point[Int, Int](42))((acc, _) => IO.absolve(acc.attempt))) must_=== 42 329 | 330 | def testDeepAsyncAbsolveAttemptIsIdentity: MatchResult[Int] = 331 | unsafePerformIO( 332 | (0 until 1000).foldLeft(IO.async[Int, Int](k => k(ExitResult.Completed(42))))((acc, _) => IO.absolve(acc.attempt)) 333 | ) must_=== 42 334 | 335 | def testDeepBindOfAsyncChainIsStackSafe: MatchResult[Int] = { 336 | val result = 0.until(10000).foldLeft(IO.point[Throwable, Int](0)) { (acc, _) => 337 | acc.flatMap( 338 | n => 339 | IO.async[Throwable, Int]( 340 | _(ExitResult.Completed[Throwable, Int](n + 1)) 341 | ) 342 | ) 343 | } 344 | 345 | unsafePerformIO(result).must_===(10000) 346 | } 347 | 348 | def testAsyncEffectReturns: MatchResult[Int] = 349 | unsafePerformIO( 350 | IO.async[Throwable, Int](cb => cb(ExitResult.Completed(42))) 351 | ).must_===(42) 352 | 353 | def testSleepZeroReturns: MatchResult[Unit] = 354 | unsafePerformIO(IO.sleep(1.nanoseconds)).must_===((): Unit) 355 | 356 | def testForkJoinIsId: MatchResult[Int] = 357 | unsafePerformIO(IO.point[Throwable, Int](42).fork.flatMap(_.join)) 358 | .must_===(42) 359 | 360 | def testDeepForkJoinIsId: MatchResult[BigInt] = { 361 | val n = 20 362 | 363 | unsafePerformIO(concurrentFib(n)).must_===(fib(n)) 364 | } 365 | 366 | def testNeverIsInterruptible: MatchResult[Int] = { 367 | val io = 368 | for { 369 | fiber <- IO.never[Throwable, Int].fork[Throwable] 370 | _ <- fiber.interrupt(ExampleError) 371 | } yield 42 372 | 373 | unsafePerformIO(io).must_===(42) 374 | } 375 | 376 | def testRaceOfValueNever: Boolean = 377 | unsafePerformIO(IO.point(42).race(IO.never[Throwable, Int])) == 42 378 | 379 | def testRepeatedPar: MatchResult[Int] = { 380 | def countdown(n: Int): IO[Void, Int] = 381 | if (n == 0) IO.now(0) 382 | else 383 | IO.now[Void, Int](1) 384 | .par(IO.now[Void, Int](2)) 385 | .flatMap(t => countdown(n - 1).map(y => t._1 + t._2 + y)) 386 | 387 | unsafePerformIO(countdown(50)).must_===(150) 388 | } 389 | 390 | def testPar: Seq[MatchResult[Int]] = 391 | 0.to(1000).map { _ => 392 | unsafePerformIO( 393 | IO.now[Void, Int](1) 394 | .par(IO.now[Void, Int](2)) 395 | .flatMap(t => IO.now(t._1 + t._2)) 396 | ).must_===(3) 397 | } 398 | 399 | def testDeadlockRegression: MatchResult[Unit] = { 400 | import java.util.concurrent.Executors 401 | 402 | val e = Executors.newSingleThreadExecutor() 403 | 404 | for (i <- 0.until(10000)) { 405 | val t = IO.async[Void, Int] { cb => 406 | val _ = e.submit[Unit](new java.util.concurrent.Callable[Unit] { 407 | def call(): Unit = cb(ExitResult.Completed(1)) 408 | }) 409 | } 410 | unsafePerformIO(t) 411 | } 412 | 413 | e.shutdown().must_===(()) 414 | } 415 | 416 | // Utility stuff 417 | val ExampleError: Exception = new Exception("Oh noes!") 418 | 419 | def asyncExampleError[A]: IO[Throwable, A] = 420 | IO.async[Throwable, A](_(ExitResult.Failed(ExampleError))) 421 | 422 | def sum(n: Int): Int = 423 | if (n <= 0) 0 424 | else n + sum(n - 1) 425 | 426 | def deepMapPoint(n: Int): IO[Throwable, Int] = 427 | if (n <= 0) IO.point(n) else IO.point(n - 1).map(_ + 1) 428 | 429 | def deepMapNow(n: Int): IO[Throwable, Int] = 430 | if (n <= 0) IO.now(n) else IO.now(n - 1).map(_ + 1) 431 | 432 | def deepMapEffect(n: Int): IO[Throwable, Int] = 433 | if (n <= 0) IO.sync(n) else IO.sync(n - 1).map(_ + 1) 434 | 435 | def deepErrorEffect(n: Int): IO[Throwable, Unit] = 436 | if (n == 0) IO.syncThrowable(throw ExampleError) 437 | else IO.unit *> deepErrorEffect(n - 1) 438 | 439 | def deepErrorFail(n: Int): IO[Throwable, Unit] = 440 | if (n == 0) IO.fail(ExampleError) 441 | else IO.unit *> deepErrorFail(n - 1) 442 | 443 | def fib(n: Int): BigInt = 444 | if (n <= 1) n 445 | else fib(n - 1) + fib(n - 2) 446 | 447 | def concurrentFib(n: Int): IO[Throwable, BigInt] = 448 | if (n <= 1) IO.point[Throwable, BigInt](n) 449 | else 450 | for { 451 | f1 <- concurrentFib(n - 1).fork 452 | f2 <- concurrentFib(n - 2).fork 453 | v1 <- f1.join 454 | v2 <- f2.join 455 | } yield v1 + v2 456 | 457 | def AsyncUnit[E]: IO[E, Unit] = IO.async[E, Unit](_(ExitResult.Completed(()))) 458 | } 459 | -------------------------------------------------------------------------------- /project/ProjectPlugin.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | 4 | import fommil.SensiblePlugin.autoImport._ 5 | import fommil.SonatypePlugin.autoImport._ 6 | import sbtdynver.DynVerPlugin.autoImport._ 7 | import org.scalafmt.sbt.ScalafmtPlugin, ScalafmtPlugin.autoImport._ 8 | import scalafix.sbt.ScalafixPlugin, ScalafixPlugin.autoImport._ 9 | 10 | object ProjectKeys { 11 | def KindProjector = 12 | addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.7") 13 | 14 | def MonadicFor = 15 | addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.4") 16 | 17 | def SemanticDB = 18 | addCompilerPlugin( 19 | ("org.scalameta" % "semanticdb-scalac" % "4.0.0-M4").cross(CrossVersion.full) 20 | ) 21 | 22 | private val silencerVersion = "0.6" 23 | def Silencer = 24 | libraryDependencies ++= Seq( 25 | compilerPlugin("com.github.ghik" %% "silencer-plugin" % silencerVersion), 26 | "com.github.ghik" %% "silencer-lib" % silencerVersion % "provided" 27 | ) 28 | 29 | val specs2Version = "4.2.0" 30 | 31 | def extraScalacOptions(scalaVersion: String) = 32 | CrossVersion.partialVersion(scalaVersion) match { 33 | case Some((2, 12)) => 34 | Seq( 35 | "-Ywarn-unused:explicits,patvars,imports,privates,locals,implicits", 36 | "-opt:l:method,inline", 37 | "-opt-inline-from:scala.**", 38 | "-opt-inline-from:scalaz.**" 39 | ) 40 | case _ => 41 | Seq("-Xexperimental") 42 | } 43 | } 44 | 45 | object ProjectPlugin extends AutoPlugin { 46 | 47 | override def requires = 48 | fommil.SensiblePlugin && fommil.SonatypePlugin && ScalafmtPlugin && ScalafixPlugin 49 | override def trigger = allRequirements 50 | 51 | val autoImport = ProjectKeys 52 | import autoImport._ 53 | 54 | override def buildSettings = 55 | Seq( 56 | organization := "org.scalaz", 57 | crossScalaVersions := Seq("2.12.6", "2.11.12"), 58 | scalaVersion := crossScalaVersions.value.head, 59 | sonatypeGithost := (Github, "scalaz", "ioeffect"), 60 | sonatypeDevelopers := List("John de Goes"), 61 | licenses := Seq( 62 | "BSD3" -> url("https://opensource.org/licenses/BSD-3-Clause") 63 | ), 64 | startYear := Some(2017), 65 | scalafmtConfig := Some(file("project/scalafmt.conf")), 66 | scalafixConfig := Some(file("project/scalafix.conf")) 67 | ) 68 | 69 | override def projectSettings = 70 | Seq( 71 | testFrameworks in Test := Seq(TestFrameworks.Specs2, TestFrameworks.ScalaCheck, TestFrameworks.ScalaTest), 72 | MonadicFor, 73 | KindProjector, 74 | //SemanticDB, // disabling scalafix until 0.6 stabilises 75 | scalacOptions ++= Seq( 76 | //"-Yrangepos", // needed by semanticdb 77 | "-unchecked", 78 | "-explaintypes", 79 | "-Ypartial-unification", 80 | "-Xlog-free-terms", 81 | "-Xlog-free-types", 82 | "-Xlog-reflective-calls", 83 | "-language:higherKinds" 84 | ), 85 | scalacOptions ++= extraScalacOptions(scalaVersion.value), 86 | // WORKAROUND https://github.com/ghik/silencer/issues/7 87 | scalacOptions -= "-Ywarn-dead-code", 88 | libraryDependencies ++= Seq( 89 | "org.scalaz" %% "scalaz-core" % "7.2.25", 90 | "org.specs2" %% "specs2-core" % specs2Version % "test", 91 | "org.specs2" %% "specs2-matcher-extra" % specs2Version % "test" 92 | ) 93 | ) 94 | } 95 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.1.6 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | scalacOptions ++= Seq("-unchecked", "-deprecation") 2 | ivyLoggingLevel := UpdateLogging.Quiet 3 | 4 | addSbtPlugin("com.fommil" % "sbt-sensible" % "2.4.5") 5 | addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.6.0-RC3") 6 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.6.0-M5") 7 | -------------------------------------------------------------------------------- /project/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | ivyLoggingLevel := UpdateLogging.Quiet 2 | // don't forget to update the scalafix rules project too 3 | addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.3") 4 | -------------------------------------------------------------------------------- /project/scalafix.conf: -------------------------------------------------------------------------------- 1 | rules = [ 2 | DisableSyntax 3 | ExplicitResultTypes 4 | LeakingImplicitClassVal 5 | RemoveUnusedImports 6 | RemoveUnusedTerms 7 | 8 | # BLOCKED: https://github.com/scalacenter/scalafix/issues/678 9 | # MissingFinal 10 | ] 11 | 12 | DisableSyntax.keywords = [ 13 | # ioeffect touches the JVM so you don't have to... 14 | # var 15 | # null 16 | return 17 | # throw 18 | # while 19 | ] 20 | DisableSyntax.noXml = true 21 | DisableSyntax.noCovariantTypes = true 22 | DisableSyntax.noContravariantTypes = true 23 | 24 | # I'm sure John has a very good reason for this... 25 | #DisableSyntax.noValInAbstract = true 26 | 27 | DisableSyntax.noImplicitObject = true 28 | DisableSyntax.noImplicitConversion = true 29 | 30 | ExplicitResultTypes { 31 | unsafeShortenNames = true 32 | 33 | # these apply to non-implicits 34 | memberKind = [Def, Val, Var] 35 | memberVisibility = [Public, Protected] 36 | } 37 | -------------------------------------------------------------------------------- /project/scalafmt.conf: -------------------------------------------------------------------------------- 1 | maxColumn = 120 2 | align = most 3 | continuationIndent.defnSite = 2 4 | assumeStandardLibraryStripMargin = true 5 | docstrings = JavaDoc 6 | lineEndings = preserve 7 | includeCurlyBraceInSelectChains = false 8 | danglingParentheses = true 9 | spaces { 10 | inImportCurlyBraces = true 11 | } 12 | optIn.annotationNewlines = true 13 | 14 | rewrite.rules = [SortImports, RedundantBraces] 15 | 16 | --------------------------------------------------------------------------------