Where communities thrive

  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
Repo info
  • Sep 05 2019 14:43
    @typelevel-bot banned @jdegoes
  • Jan 31 2019 21:17
    codecov-io commented #484
  • Jan 31 2019 21:08
    scala-steward opened #484
  • Jan 31 2019 18:19
    andywhite37 commented #189
  • Jan 31 2019 02:41
    kamilongus starred typelevel/cats-effect
  • Jan 30 2019 00:01
    codecov-io commented #483
  • Jan 29 2019 23:51
    deniszjukow opened #483
  • Jan 29 2019 23:37
  • Jan 29 2019 23:22
  • Jan 29 2019 20:26
    Rui-L starred typelevel/cats-effect
  • Jan 29 2019 18:01
    jdegoes commented #480
  • Jan 29 2019 17:04
    thomaav starred typelevel/cats-effect
  • Jan 28 2019 17:43
    asachdeva starred typelevel/cats-effect
  • Jan 28 2019 07:12
    alexandru commented #480
  • Jan 28 2019 05:45
    codecov-io commented #482
  • Jan 28 2019 05:35
    daron666 opened #482
  • Jan 27 2019 13:56
    codecov-io commented #481
  • Jan 27 2019 13:46
    lrodero opened #481
  • Jan 27 2019 05:47
    codecov-io commented #460
  • Jan 27 2019 05:37
    codecov-io commented #460
Daniel Spiewak
more abstractly, IO is two free monads glued together with an interpreter that can sequence back and forth between them. Cont is itself a free monad for a coalgebra, while Free is obviously… uh, Free, and we instantiate it with a trivial algebra
the run loop today behaves radically different than this though
the naive run loop structure for the above type works, but is very slow and also sometimes quite tricky to get right (e.g. stack safety for raiseError is hard with the above)
the more Enterprise Grade runloop structure requires a lot more groundwork :-)
I'm not sure exactly what you're looking for
Fabio Labella
I find the actual runloop to be easier to understand than the type above :smile:
Daniel Spiewak
I think the above is only confusing because Cont is so bizarre
and ContT is even more bizarre
Fabio Labella
yeah if you unroll it it makes sense
Daniel Spiewak
I have an old, old, old gist that I put together with Brian McKenna where we reimplemented scalaz.Task in terms of scalaz's ContT and scalaz.effect.IO, for maximum yoloswag
it had an exceptionally cool bit where we defined Task.liftIO
and it was literally just flatMap on ContT
which I thought was clever
Fabio Labella

I would start looking at the runloop from these 3 points:

  • IO it's a tree that gets translated, and each node in the tree represent a fundamental concept (so you have Async, Delay, Attempt, Pure, FlatMap)
  • The runloop keeps a state, which is roughly case class State(current: IO[Any], stack: List[Any => IO[Any]]. Each iteration pattern matches on current, does some stuff, and keeps going by taking the next off the stack.
  • The (slightly simplified) type of Async is case class Async(f: (Either[Throwable, A] => Unit) => Unit) extends IO[A]. The key point is that Async does not introduce any asynchrony, it just wraps something that can already do asynchrony by itself. It's kinda funny in that Asyncdoes most of the work for the "complex" stuff, but actually the only thing it does is passing a function to another function.

I can expand and unpack some of this if needed

Julien Truffaut
thanks for the answers. Yeah, I think the double free monad will come later.
@SystemFw I think I get all these points, what's not really clear to me is what to do in the run loop when the current IO is an Async
e.g. if it is the top level one
Fabio Labella
@julien-truffaut the async node contains a function that takes a callback
the only thing you can do with a function is to call it
so what you do is pass a callback to it
if it's the top one, the callback is passed to you, which is why runAsync takes one
like all forms of CPS is a bit mind bendy
does that make any sense? (feel free to say no, maybe we can work through an example)
Julien Truffaut
yes the runAsync case makes sense
I am less sure what happens for the runSync
Fabio Labella
you still pass a callback
"you" meaning the runloop
actually it's quicker to just show you
 val latch = new OneShotLatch
 var ref: Either[Throwable, A] = null

    ioa unsafeRunAsync { a =>
      // Reading from `ref` happens after the block on `latch` is
      // over, there's a happens-before relationship, so no extra
      // synchronization is needed for visibility
      ref = a
so in this case you have to rely on the native platform "waiting" mechanism
for the JVM, this is thread blocking
for JS, it's unsupported and you just throw UnsupportedOperationException
below those lines, you try and acquire that latch, which blocks the thread until it gets released
at which point you return the contents of ref
Fabio Labella
so basically unsafeRunSync is "create shared state, create waiting condition, run asynchronously (sets state and releases waiting condition), wait on condition, return shared state"
makes any sense?
Julien Truffaut
yes I think it does, thanks for the explanation. I need to read more about latches and OneShotLatch. I have seen it but I have never used it
Fabio Labella
it's basically Deferred
but with thread blocking instead of fiber blocking
so if you look at toIO on ConcurrentEffect, it's the same mechanism but "one level up"
wait, I actually I think that was reimplemented with async directly now. It used to be Deferred though
Julien Truffaut
ah I was looking for OneShotLatch, didn't notice it was defined inline
so this is the code that waits for the latch to be released either indefinitely or a certain amount of time
case Duration.Inf =>
case f: FiniteDuration if f > Duration.Zero =>
        blocking(latch.tryAcquireSharedNanos(1, f.toNanos))
Fabio Labella
I skipped a few layers of indirection
unsafeRunSync is unsafeRunTimed(Duration.Inf) and there are some other details
but the key idea is not affected by those
Julien Truffaut
awesome I get it
Fabio Labella
nice :)