Git Product home page Git Product logo

ioeffect's Introduction

Build Status

Backport of the IO Monad to scalaz 7.2.

There are no binary or source compatibility guarantees between releases of this preview.

libraryDependencies += "org.scalaz" %% "scalaz-ioeffect" % "<version>"

where <version> is the latest on maven central.

scalaz-ioeffect

The scalaz.ioeffect package provides a general-purpose effect monad and associated abstractions for purely functional Scala applications.

The package strives to deliver on the following design goals:

  • Principled. A purely functional interface for effectful code with rigorous, well-defined semantics.
  • Performant. A low-level, highly-optimized runtime system that offers performance better than or comparable to other effect monads.
  • Pragmatic. The composable, orthogonal primitives necessary to build real world software, including primitives for concurrent and asynchronous programming.

Why IO?

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.

However, there are many practical reasons to build your programs using IO, including all of the following:

  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.
  • 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.

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.

Use IO because it's simply not practical to write real-world, correct software without it.

Introduction

A value of type IO[E, A] describes an effect that may fail with an E, run forever, or produce a single A.

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.

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).

Usage

Main

Your main function can extend SafeApp, which provides a complete runtime system and allows your entire program to be purely functional.

import scalaz.ioeffect.{IO, SafeApp}
import scalaz.ioeffect.console._

import java.io.IOException

object MyApp extends SafeApp {

  def run(args: List[String]): IO[Void, ExitStatus] =
    myAppLogic.attempt.map(_.fold(_ => 1, _ => 0)).map(ExitStatus.ExitNow(_))

  def myAppLogic: IO[IOException, Unit] =
    for {
      _ <- putStrLn("Hello! What is your name?")
      n <- getStrLn
      _ <- putStrLn("Hello, " + n + ", good to meet you!")
    } yield ()
}

Pure Values

You can lift pure values into IO with IO.point:

val liftedString: IO[Void, String] = IO.point("Hello World")

The constructor uses non-strict evaluation, so the parameter will not be evaluated until when and if the IO action is executed at runtime.

Alternately, you can use the IO.now constructor for strict evaluation:

val lifted: IO[Void, String] = IO.now("Hello World")

You should never use either constructor for importing impure code into IO. The result of doing so is undefined.

Impure Code

You can use the sync method of IO to import effectful synchronous code into your purely functional program:

val nanoTime: IO[Void, Long] = IO.sync(System.nanoTime())

If you are importing effectful code that may throw exceptions, you can use the syncException method of IO:

def readFile(name: String): IO[Exception, ByteArray] =
  IO.syncException(FileUtils.readBytes(name))

The syncCatch method is more general, allowing you to catch and optionally translate any type of Throwable into an error type.

You can use the async method of IO to import effectful asynchronous code into your purely functional program:

def makeRequest(req: Request): IO[HttpException, Response] =
  IO.async(cb => Http.req(req, cb))

Mapping

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.

val answer = IO.point(21).map(_ * 2)

You can transform an IO[E, A] into an IO[E2, A] by calling the leftMap method with a function E => E2:

val response: IO[AppError, Response] =
  makeRequest(r).leftMap(AppError(_))

Chaining

You can execute two actions in sequence with the flatMap method. The second action may depend on the value produced by the first action.

val contacts: IO[IOException, IList[Contact]] =
  readFile("contacts.csv").flatMap((file: ByteArray) =>
    parseCsv(file).map((csv: IList[CsvRow]) =>
      csv.map(rowToContact)
    )
  )

You can use Scala's for comprehension syntax to make this type of code more compact:

val contacts: IO[IOException, IList[Contact]] =
  for {
    file <- readFile("contacts.csv")
    csv  <- parseCsv(file)
  } yield csv.map(rowToContact)

Failure

You can create IO actions that describe failure with IO.fail:

val failure: IO[String, Unit] = IO.fail("Oh noes!")

Like all IO values, these are immutable values and do not actually throw any exceptions; they merely describe failure as a first-class value.

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.

The scalaz.Void type makes a suitable choice to describe computations that cannot fail:

val file: IO[Void, Data] = readData("data.json").attempt[Void].map {
  case -\/ (_)    => IO.point(NoData)
  case  \/-(data) => IO.point(data)
}

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]:

def sqrt(io: IO[Void, Double]): IO[NonNegError, Double] =
  IO.absolve(
    io[NonNegError].map(value =>
      if (value < 0.0) -\/(NonNegError)
      else \/-(Math.sqrt(value))
    )
  )

If you want to catch and recover from all types of errors and effectfully attempt recovery, you can use the catchAll method:

openFile("primary.json").catchAll(_ => openFile("backup.json"))

If you want to catch and recover from only some types of exceptions and effectfully attempt recovery, you can use the catchSome method:

openFile("primary.json").catchSome {
  case FileNotFoundException(_) => openFile("backup.json")
}

You can execute one action, or, if it fails, execute another action, with the orElse combinator:

val file = openFile("primary.json").orElse(openFile("backup.json"))

Retry

There are a number of useful combinators for repeating actions until failure or success:

  • IO.forever — Repeats the action until the first failure.
  • IO.retry — Repeats the action until the first success.
  • IO.retryN(n) — Repeats the action until the first success, for up to the specified number of times (n).
  • IO.retryFor(d) — Repeats the action until the first success, for up to the specified amount of time (d).

Brackets

Brackets are a built-in primitive that let you safely acquire and release resources.

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.

Brackets consist of an acquire action, a utilize action (which uses the acquired resource), and a release action.

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.

openFile("data.json").bracket(closeFile(_)) { file =>
  for {
    data    <- decodeData(file)
    grouped <- groupData(data)
  } yield grouped
}

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.

A helper method called ensuring provides a simpler analogue of finally:

val composite = action1.ensuring(cleanupAction)

Fibers

To perform an action without blocking the current process, you can use fibers, which are a lightweight mechanism for concurrency.

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.

val analyzed =
  for {
    fiber1   <- analyzeData(data).fork  // IO[E, Analysis]
    fiber2   <- validateData(data).fork // IO[E, Boolean]
    ... // Do other stuff
    valid    <- fiber2.join
    _        <- if (!valid) fiber1.interrupt(DataValidationError(data))
                else IO.unit
    analyzed <- fiber1.join
  } yield analyzed

On the JVM, fibers will use threads, but will not consume unlimited threads. Instead, fibers yield cooperatively during periods of high-contention.

def fib(n: Int): IO[Void, Int] =
  if (n <= 1) IO.point(1)
  else for {
    fiber1 <- fib(n - 2).fork
    fiber2 <- fib(n - 1).fork
    v2     <- fiber2.join
    v1     <- fiber1.join
  } yield v1 + v2

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.

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).

Error Model

The IO error model is simple, consistent, permits both typed errors and termination, and does not violate any laws in the Functor hierarchy.

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.

Separately from errors of type E, a fiber may be terminated for the following reasons:

  • 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.
  • 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.
  • The fiber has a defect that leads to a non-recoverable error. There are only two ways this can happen:
    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.
    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.

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.

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.

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.

Parallelism

To execute actions in parallel, the par method can be used:

def bigCompute(m1: Matrix, m2: Matrix, v: Matrix): IO[Void, Matrix] =
  for {
    t <- computeInverse(m1).par(computeInverse(m2))
    val (i1, i2) = t
    r <- applyMatrices(i1, i2, v)
  } yield r

The par combinator has resource-safe semantics. If one computation fails, the other computation will be interrupted, to prevent wasting resources.

Racing

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.

action1.race(action2)

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.

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.

Performance

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.

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.

As of the time of this writing, IO is significantly faster than or at least comparable to all other purely functional solutions.

Stack Safety

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.

map flatMap
sync 10,000 iterations unlimited
async unlimited unlimited

Thread Shifting - JVM

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.

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.

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.

These defaults help guarantee stack safety and cooperative multitasking. They can be changed in RTS if automatic thread shifting is not desired.

ioeffect's People

Contributors

barambani avatar danielyli avatar edmundnoble avatar emilypi avatar fommil avatar jmcardon avatar ktonga avatar nequissimus avatar xuwei-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ioeffect's Issues

Thread pool Shifting

@jdegoes I'm wondering is it possible or will be possible in future to shift thread pools? (one thread pool for CPU intense load, another one for blocking operations, etc).

unwanted printlns in the default handler

I'm using IO.syncThrowable to invoke a legacy method that can throw an exception.

But even though that's supposed to capture the execption as a valid Task error, not an unexpected exception, I'm still getting the stacktrace dumped to the screen with

scalaz.ioeffect.Errors$UnhandledError: An error was not handled by a fiber: 

I can try to minimise it with an example if this is difficult to see by inspection.

Behave of catchAll

I´m learning the monad IO of scalaZ and I cannot understand how catchAll and catchSome operators works.
I was expecting so see a behave like the onError or onErrorrResumeNext of RxJava, but instead is not catching the throwable, and it´s just breaking the test and throwing the NullPointerException.

Here my two examples

  @Test
  def catchAllOperator(): Unit = {
    val errorSentence: IO[Throwable, String] =
      IO.point[Throwable, String](null)
        .map(value => value.toUpperCase())
        .catchAll(error => IO.fail(error))
    println(unsafePerformIO(errorSentence))

  }

And catchSome example

  @Test
  def catchSomeOperator(): Unit = {
    val errorFunction = new PartialFunction[Throwable /*Entry type*/ , IO[Throwable, String] /*Output type*/ ] {

      override def isDefinedAt(x: Throwable): Boolean = x.isInstanceOf[NullPointerException]

      override def apply(v1: Throwable): IO[Throwable, String] = IO.point("Default value")
    }

    val errorSentence = IO.point[Throwable, String](null)
      .map(value => value.toUpperCase())
      .catchSome(errorFunction)
    println(unsafePerformIO(errorSentence))

  }

Any idea what I´m doing wrong?.

Regards

IO.fromFuture

when integrating with existing code, having the ability to create a Task from some Future thing is essential.

But we don't seem to have anything like this... can we please add this?

I'm trying to migrate my work codebase to scalaz.ioeffect from cats-effect right now, so if there are any snippets that I can use as a workaround please do let me know.

note that cats has fromFuture[A](iofa: IO[Future[A]]): IO[A] = ...

which allows deciding if the future begins now or delayed.

IO requires Bimap

We currently do not have a bimap for IO. As a bifunctor this is necessary for calling it a bifunctor. If someone wants to implement the new tag and case of the IO runtime supporting it (for instance, as was done with the Map and FlatMap tags), that would be greatly appreciated.

`IO`s created with `par` fail with `ClassCastException` if performed within loop

So it seems something is going on wrong with par implementation, when performed in isolation it seems to work but it starts having unpredictable behavior when performed multiple consecutive times, I realized about this since I have some property-based test with default sampling of 100 performing some IO using par combinator.

You can find the test i'm talking about here: https://github.com/ktonga/scala-poc/blob/topic/parallel-io/src/test/scala/com/brickx/autoinvest/ProgramSpec.scala#L26

This tests used to run ok when IOs were combined using apply2 from default Applicative instance but started failing after I introduced the use of par in this WIP PR: ktonga/scala-poc#6

I'll try to add a minimal example that reproduces the bug to this project for easy debugging.

Cheers.

Help me understand racing :)

OK, so here I thought race was straight-forward. Clearly I am doing something wrong...

import $ivy.`org.scalaz::scalaz-ioeffect:2.1.0`, scalaz._, ioeffect._, Scalaz._, scala.concurrent.duration._

object App extends SafeApp {
    type Error = Exception
  
    def run(args: List[String]): IO[Error, Unit] = {
      val io1 = IO.sleep[Error](10.seconds).fork[Error]
      val io2 = IO.point[Error, Unit]{ println("Hello"); Thread.sleep(500L) }.forever[Unit].fork[Error]
      io1.race(io2).toUnit
}

App.main(Array.empty)

I was under the impression the two IO would race each other and once the first one finishes (clearly the sleeper, since the other one is a forever guy), the slower one is interrupted and will never be heard of again.
In the above code io2 is completely unfazed by the race and will keep on running forever. Can an IO.forever not be interrupted?

Anyways, I changed things around a bit, thinking maybe one of the fibres is not allowed to be forked, and now I have this:

import $ivy.`org.scalaz::scalaz-ioeffect:2.1.0`, scalaz._, ioeffect._, Scalaz._, scala.concurrent.duration._

object App extends SafeApp {
  type Error = Exception

  def run(args: List[String]): IO[Error, Unit] = {
    val io1: IO[Error, Unit] = IO.sleep[Error](10.seconds).fork[Error].toUnit
    val io2: IO[Error, Unit] = IO.point(println("Hello")).forever[Unit]
    io1.race(io2)
  }
}

App.main(Array.empty)

a) The race lasts maybe 1 second, not 10, not forever
b) Sometimes I get a bunch of prints but usually only one


Here is essentially what I need (maybe there is a significantly better way to do this):

  • I have two IO[Error, Unit] (let's call them X and Y)
  • X runs forever (it's a streaming processor)
  • Y does a bunch of stuff, then completes
  • I need X to start, then Y to start. Once Y has completed, interrupt X, collect results, done

scaladoc is empty

🤔 ?

$ wget https://oss.sonatype.org/content/repositories/releases/org/scalaz/scalaz-ioeffect_2.12/2.0.0/scalaz-ioeffect_2.12-2.0.0-javadoc.jar
$ unzip -Z scalaz-ioeffect_2.12-2.0.0-javadoc.jar 
Archive:  scalaz-ioeffect_2.12-2.0.0-javadoc.jar
Zip file size: 189 bytes, number of entries: 1
-rw----     2.0 fat       25 bX defN 18-Apr-20 23:16 META-INF/MANIFEST.MF
1 file, 25 bytes uncompressed, 27 bytes compressed:  -8.0%

<url> Some(http://github.com/scalaz/effect) </url>

sbt makePom
cat target/scala-2.12/scalaz-ioeffect_2.12-0.0.1-SNAPSHOT.pom
<?xml version='1.0' encoding='UTF-8'?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.scalaz</groupId>
    <artifactId>scalaz-ioeffect_2.12</artifactId>
    <packaging>jar</packaging>
    <description>scalaz-ioeffect</description>
    <url>http://github.com/scalaz/effect</url>
    <version>0.0.1-SNAPSHOT</version>
    <licenses>
        <license>
            <name>BSD3</name>
            <url>https://opensource.org/licenses/BSD-3-Clause</url>
            <distribution>repo</distribution>
        </license>
    </licenses>
    <name>scalaz-ioeffect</name>
    <inceptionYear>2017</inceptionYear>
    <organization>
        <name>org.scalaz</name>
        <url>http://github.com/scalaz/effect</url>
    </organization>
    <scm>
        <url> Some(http://github.com/scalaz/effect) </url>

addSbtPlugin("com.fommil" % "sbt-sensible" % "2.4.1") bug ? 🤔
https://gitlab.com/fommil/sbt-sensible/blob/36ed88c76e7b249571b951c8ac653ba66b5b00e7/src/main/scala/SonatypePlugin.scala#L131
/cc @fommil

Request: Scala Native support

I’m building an app using Scala Native, and pure FP IO is something I’d like to use. It would be great for scalaz-ioeffect to support Scala Native 0.3 like scalaz-core, scalaz-effect, scalaz-iteratee, etc. How difficult is this from a technical perspective?

Minor typo in readme

Readme currently reads:

  def run(args: List[String]): IO[Void, ExitStatus] =
    myAppLogic.attempt.map(_.fold(_ => 1)(_ => 0)).map(ExitStatus.ExitNow(_))

It should read:

  def run(args: List[String]): IO[Void, ExitStatus] =
    myAppLogic.attempt.map(_.fold(_ => 1, _ => 0)).map(ExitStatus.ExitNow(_))

MonadPlus instance seems wrong

I think we can only provide MonadPlus when E is Unit. Otherwise we can't differentiate between failures and the empty failure, or maybe our Plus logic needs to be more advanced.

MonadIO instances

I wonder if perhaps we should remove the extends Monad to be able to create instances for the scalaz mtl

Can't compile the main example

I can't compile the example code provided in the README.
I get the following compilation error:

[error] /MyApp.scala:10: overriding method run in trait SafeApp of type (args: List[String])scalaz.ioeffect.IO[scalaz.ioeffect.Void, MyApp.ExitStatus];
[error]  method run has incompatible type
[error]   def run(args: List[String]): IO[Void, ExitStatus] =
[error]       ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed
[error] Total time: 2 s, completed Feb 13, 2019 4:34:13 PM

And the following in IntelliJ:

Overriding type List[String] => IO[Void, MyApp.ExitStatus] does not conform to base type List[String] => IO[ioeffect.Void, SafeApp.this.ExitStatus]

Just to be clear, here is the example code from the README that I'm trying to run:

import scalaz.ioeffect.{IO, SafeApp}
import scalaz.ioeffect.console._

import java.io.IOException

object MyApp extends SafeApp {

  def run(args: List[String]): IO[Void, ExitStatus] =
    myAppLogic.attempt.map(_.fold(_ => 1, _ => 0)).map(ExitStatus.ExitNow(_))

  def myAppLogic: IO[IOException, Unit] =
    for {
      _ <- putStrLn("Hello! What is your name?")
      n <- getStrLn
      _ <- putStrLn("Hello, " + n + ", good to meet you!")
    } yield ()
}

I am using Scala 2.11.7 and Scalaz 7.2.27

SNAPSHOT instructions

rather that publish binaries, we should note that this is how to declare a source dependency

val ioeffect = ProjectRef(
  uri("git://github.com/scalaz/ioeffect.git#1a3b397"),
  "ioeffect"
)

and then .dependsOn(ioeffect) instead of libraryDependencies

Fiber compositions does not return any value

Sorry about open here this issue, but to be honest there's no community mature enough in StackOverFlow to help on issues related with this library.

I'm trying to do an example of composition of Fibers, based in the for comprehension that you provide with fibboinachi example

I manage to make it work the fibbonachi, but if you execute this one is not returning anything.
most probably it will be something silly so sorry in advance XD!

@Test
def compositionOfFibersFeature(): Unit = {
  println(s"Before ${Thread.currentThread().getName}")
  def composition: IO[Throwable, String] = for {
    fiber <- createIO("Business logic 1").fork
    fiber1 <- createIO("Business logic 2").fork
    v2 <- fiber1.join
    v1 <- fiber.join
  } yield v1 + v2
  println(s"After: ${Thread.currentThread().getName}")
  unsafePerformIO(composition)
}

private def createIO(sentence:String):IO[Throwable,String] = {
  IO.point[Throwable, String](sentence)
    .map(sentence => {
      sentence.concat(s"$sentence ${Thread.currentThread().getName}").toUpperCase()
    }).delay(1 second)
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.