Skip to content

Commit

Permalink
Merge pull request #5 from Ragazoor/update
Browse files Browse the repository at this point in the history
Update
  • Loading branch information
Ragazoor authored Mar 10, 2024
2 parents 6d4babf + 383bfe4 commit 6d7642e
Show file tree
Hide file tree
Showing 17 changed files with 427 additions and 58 deletions.
72 changes: 56 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Scala IO
# An error typed Future
A Future based monad with typed errors.
Designed to be a drop in replacement for `Future`,
built entirely on top of `Future` with the same performance,
effortlessly integrates into existing `Future` based libraries.
Designed to be a replacement for the `scala.concurrent.Future` with minimal
migration needed. Entirely built on top of the `scala.concurrent.Future` it has
the same performance and easily integrates into existing `Future` based libraries.

# Installation

Expand All @@ -13,15 +13,30 @@ libraryDependencies += "dev.ragz" %% "io" % "0.1.0"
```

# Getting Started
Compile and or run test

```shell
sbt compile
```

```shell
sbt test
```
## Examples
```scala
import dev.ragz.io.implicits._

val successIO: IO[Nothing, Int] = IO.successful(a + 1)
val failedIO: IO[Throwable, Nothing] = IO.failed(new Exception("error"))
val typedFailureIO: IO[RunTimeException, Nothing] = IO.failed(new RuntimeException("error"))
val TaskIO: IO[Throwable, Int] = IO(1)
val standardFuture: scala.concurrent.Future[Int] = successIO.toFuture
val ioFromFuture: IO[Throwable, Int] = standardFuture.io
```
This is the basics of using `IO` in your code. The IO has the same API
as the `Future`, together with the type alias
`type Future[+A] = IO[Throwable, A]` makes the type compatible with the `Future`.
as the `scala.concurrent.Future`, and thanks to the type alias
`type Future[+A] = IO[Throwable, A]` we don't need to rename `Future`s
all over the code base.
## Error handling

```scala
Expand All @@ -32,15 +47,40 @@ val runTimeExceptinIO: IO[RunTimeException, Nothing] =
}

```
```scala
Compile and or run test

```shell
sbt compile
## Migration
The goal is to eventually be able to replace `scala.concurrent`, however we
not everything is available yet. If you are only using `Future`,
`ExecutionContext` and `NonFatal` you can use the following to migrate
most of the code:
```text
replace:
import.scala.concurrent.*
with:
import io.github.ragazoor.*
import io.github.ragazoor.implicits.*
import io.github.ragazoor.migration.implicits.*
```

```shell
sbt test
There are a few occurrences where we need to manually fix the code:
- If we are using a third-party library returning a `scala.concurrent.Future`
we need to convert it to `IO` using `.io` and the implicit in
`ragazoor.implicits.*`.
- If there are async tests using `scala.concurrent.Future` but does not
have `scala.concurrent` in imported we need to add
`import io.github.ragazoor.migration.implicits.*`.
- If you are using implicit classes that are extending
`scala.concurrent.Future` the compiler will not be able to convert
like one might think using the migration implicits. So we need to make
it explicit:
```scala
implicit class MyImplicitClassFunction(f: Future[Int]) {
def bar: Future[Option[Int]] = f.map(Some(_))
}
def foo: IO[Throwable, Int] = ???
val a: IO[Throwable, Option[Int]] = foo.myImplicitClassFunction.io // does not compile
val a: IO[Throwable, Option[Int]] = foo.toFuture.myImplicitClassFunction.io // compiles
```

## Benchmarks
Expand All @@ -58,9 +98,9 @@ Example benchmark
[info] FutureBenchmark.futureMap thrpt 10 27.629 ± 0.490 ops/s
[info] FutureBenchmark.futureRecover thrpt 10 24.488 ± 0.415 ops/s
[info] FutureBenchmark.futureSequence thrpt 10 2.004 ± 0.203 ops/s
[info] FutureBenchmark.resultFlatMap thrpt 10 22.395 ± 0.375 ops/s
[info] FutureBenchmark.resultMap thrpt 10 27.328 ± 0.455 ops/s
[info] FutureBenchmark.resultMapError thrpt 10 27.177 ± 0.041 ops/s
[info] FutureBenchmark.resultSequence thrpt 10 1.817 ± 0.029 ops/s
[info] FutureBenchmark.ioFlatMap thrpt 10 22.395 ± 0.375 ops/s
[info] FutureBenchmark.ioMap thrpt 10 27.328 ± 0.455 ops/s
[info] FutureBenchmark.ioMapError thrpt 10 27.177 ± 0.041 ops/s
[info] FutureBenchmark.ioSequence thrpt 10 1.817 ± 0.029 ops/s
[success] Total time: 1623 s (27:03), completed Feb 20, 2024, 7:02:20 PM
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.ragz.io
package io.github.ragazoor

import org.openjdk.jmh.annotations._

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.ragz.io
package io.github.ragazoor

object IOFailedException {
private[io] type IOFailedException = NoSuchElementException with FatalError
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.ragz.io
package io.github.ragazoor

object IOFailedException {
private[io] type IOFailedException = NoSuchElementException | FatalError
Expand Down
23 changes: 0 additions & 23 deletions result/src/main/scala/dev/ragz/io/implicits.scala

This file was deleted.

5 changes: 0 additions & 5 deletions result/src/main/scala/dev/ragz/io/package.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package dev.ragz.io
package io.github.ragazoor

private[io] final case class FatalError(e: Throwable) extends Throwable(e)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.ragz.io
package io.github.ragazoor

import scala.concurrent.{ ExecutionContext, Future => StdFuture }
import scala.util.Try
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dev.ragz.io
package io.github.ragazoor

import dev.ragz.io.IOFailedException.IOFailedException
import IOFailedException.IOFailedException

import scala.concurrent.ExecutionContext.parasitic
import scala.concurrent.duration.Duration
Expand All @@ -17,6 +17,9 @@ sealed trait IO[+E <: Throwable, +A] extends Awaitable[A] {
def map[B](f: A => B)(implicit ec: ExecutionContext): IO[E, B] =
IO(self.toFuture.transform(_ map f))

def mapTry[B](f: A => Try[B])(implicit ec: ExecutionContext): IO[Throwable, B] =
IO(self.toFuture.transform(_ flatMap f))

def mapEither[E2 >: E <: Throwable, B](f: A => Either[E2, B])(implicit ec: ExecutionContext): IO[E2, B] =
IO(self.toFuture.transform(_ flatMap (f(_).toTry)))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.ragz.io
package io.github.ragazoor

import scala.util.control

Expand Down
10 changes: 10 additions & 0 deletions result/src/main/scala/io/github/ragazoor/implicits.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.ragazoor

import scala.concurrent.{ Future => StdFuture }

object implicits {
implicit class StdFutureToIo[A](val future: StdFuture[A]) {
def io: IO[Throwable, A] = IO.fromFuture(future)
}

}
Loading

0 comments on commit 6d7642e

Please sign in to comment.