Skip to content

Instantly share code, notes, and snippets.

@SystemFw
Last active June 1, 2019 03:15
Show Gist options
  • Save SystemFw/256205f51bf0135e4c6fd95dee4590fc to your computer and use it in GitHub Desktop.
Save SystemFw/256205f51bf0135e4c6fd95dee4590fc to your computer and use it in GitHub Desktop.

Revisions

  1. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -80,7 +80,7 @@ Apart from the fact that we can't call `Async.shift` anymore so we can't impleme

    This doesn't work either:
    ```scala
    def blocking[F[_]: Async, G[_]: Sync A](p: G[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A]
    def blocking[F[_]: Async, G[_]: Sync, A](p: G[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A]
    ```
    because we would need to `flatMap` `F` and `G` in the same for comprehension, and there's no way to unify those. It also suffers from the same problem, we can pass any `G1: Async` to `G` and the `Sync` constraint will be respected, so we aren't preventing `shift` calls in `G`

  2. SystemFw revised this gist Jun 15, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -76,13 +76,13 @@ Note that if we change the signature of `blocking` to (note the `Sync` constrain
    ```scala
    def blocking[F[_]: Sync, A](p: F[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A]
    ```
    We solve the problem for `p`, but can't call `shift` anymore so we can't implement it.
    Apart from the fact that we can't call `Async.shift` anymore so we can't implement it, we could still pass `IO` (which is `Async` and can contain calls to `shift`) and the `F[_]: Sync` constraint would be respected, so that's not enough to ensure only `Sync` datatypes are accepted.

    This doesn't work either:
    ```scala
    def blocking[F[_]: Async, G[_]: Sync A](p: G[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A]
    ```
    because we would need to `flatMap` `F` and `G` in the same for comprehension, and there's no way to unify those.
    because we would need to `flatMap` `F` and `G` in the same for comprehension, and there's no way to unify those. It also suffers from the same problem, we can pass any `G1: Async` to `G` and the `Sync` constraint will be respected, so we aren't preventing `shift` calls in `G`

    ## RankN types
    What we need is a RankN type: we want `p` to be passed as a polymorphic value, so that it's the _callee_ (`blocking`) that decides
  3. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -60,7 +60,7 @@ time, so let's write a combinator for it (for those familiar with cats-effect, I
    ```scala
    def blocking[F[_]: Async, A](p: F[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A] =
    for {
    _ <- Async.shift[F](blockingEc)
    _ <- Async.shift[F](blockingEC)
    res <- p
    _ <- Async.shift[F]
    } yield res
  4. SystemFw revised this gist Jun 15, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -47,7 +47,7 @@ An example of "shift-and-shift-back" would be:
    for {
    _ <- Async.shift[F](blockingEC)
    myType <- dbCall[F]
    _ <- Async.shift(global)
    _ <- Async.shift[F](global)
    res <- post[F](myType)
    } yield res
    ```
    @@ -62,7 +62,7 @@ def blocking[F[_]: Async, A](p: F[A], blockingEC: ExecutionContext)(implicit ec:
    for {
    _ <- Async.shift[F](blockingEc)
    res <- p
    _ <- Async.shift
    _ <- Async.shift[F]
    } yield res
    ```

  5. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -95,7 +95,7 @@ blocking :: Async f => (forall g. Sync g => g a) => BlockingEC => EC => f a
    ```
    the caller to `blocking` can't pass a potentially async value like `IO[Int]` as the `g a` to `blocking`, because `blocking` requires the value
    to be fully polymorphic in `g`, but it can pass a value defined over any `G[_]: Sync`.
    However, _inside_ the body of `blocking` `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Sync` extends `Async`, `f` is a
    However, _inside_ the body of `blocking` `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Async` extends `Sync`, `f` is a
    valid type. So basically we get what we want: `blocking` can call `shift`, and is assured that `g a` is a synchronous value that doesn't
    do any shifting itself.

  6. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -45,7 +45,7 @@ An example of "shift-and-shift-back" would be:
    ```scala
    def prog[F[_]: Async]: F[HttpResponse] =
    for {
    _ <- Async.shift[F](blockingEC]
    _ <- Async.shift[F](blockingEC)
    myType <- dbCall[F]
    _ <- Async.shift(global)
    res <- post[F](myType)
  7. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -21,7 +21,7 @@ The design of `cats-effect` is oriented towards non-blocking functionality, and
    primitives that don't actually block the underlying JVM thread. This allows users to run an arbitrary number of `IO`s on top of a CPU bound
    pool like `global`, and even on a single thread (like in scala.js). However, when interacting with legacy Java apis, one often
    deals with code that blocks JVM threads. The most common pattern to deal with this situation, after wrapping the blocking code in `F[_]: Sync`,
    is to run it on a custom thread pool that's specific to blocking code, and then run the rest of the computation back in the "normal" pool.
    is to run it on a custom thread pool that's specific to blocking code, and then run the rest of the computation back on the "normal" pool.
    This pattern is informally called "shift-and-shift-back", and relies on a function called `shift`, which can be defined for any `F[_]: Async`
    ```scala
    def shift[F[_]](ec: ExecutionContext)(implicit F: Async[F]): F[Unit] =
  8. SystemFw revised this gist Jun 15, 2018. 1 changed file with 3 additions and 1 deletion.
    4 changes: 3 additions & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -96,8 +96,10 @@ blocking :: Async f => (forall g. Sync g => g a) => BlockingEC => EC => f a
    the caller to `blocking` can't pass a potentially async value like `IO[Int]` as the `g a` to `blocking`, because `blocking` requires the value
    to be fully polymorphic in `g`, but it can pass a value defined over any `G[_]: Sync`.
    However, _inside_ the body of `blocking` `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Sync` extends `Async`, `f` is a
    valid value. So basically we get what we want: `blocking` can call `shift`, and is assured that `g a` is a synchronous value that doesn't
    valid type. So basically we get what we want: `blocking` can call `shift`, and is assured that `g a` is a synchronous value that doesn't
    do any shifting itself.


    Apologies for the lack of Scala in this last paragraph, but Scala doesn't support RankN types or polymorphic values, and I think this is
    a compelling example (there are ways to simulate this specific scenario I think).
    There are many other use cases though, ranging from `Free.foldMap` to the `ST monad`, to safe resource region that are enforced at compile
  9. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -95,7 +95,7 @@ blocking :: Async f => (forall g. Sync g => g a) => BlockingEC => EC => f a
    ```
    the caller to `blocking` can't pass a potentially async value like `IO[Int]` as the `g a` to `blocking`, because `blocking` requires the value
    to be fully polymorphic in `g`, but it can pass a value defined over any `G[_]: Sync`.
    However, _inside_ blocking `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Sync` extends `Async`, `f` is a
    However, _inside_ the body of `blocking` `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Sync` extends `Async`, `f` is a
    valid value. So basically we get what we want: `blocking` can call `shift`, and is assured that `g a` is a synchronous value that doesn't
    do any shifting itself.
    Apologies for the lack of Scala in this last paragraph, but Scala doesn't support RankN types or polymorphic values, and I think this is
  10. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -94,7 +94,7 @@ The type we need in Haskell can be written like so:
    blocking :: Async f => (forall g. Sync g => g a) => BlockingEC => EC => f a
    ```
    the caller to `blocking` can't pass a potentially async value like `IO[Int]` as the `g a` to `blocking`, because `blocking` requires the value
    to be fully polymorphic in `g`, but it can pass a value defined over any `Sync g => g`.
    to be fully polymorphic in `g`, but it can pass a value defined over any `G[_]: Sync`.
    However, _inside_ blocking `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Sync` extends `Async`, `f` is a
    valid value. So basically we get what we want: `blocking` can call `shift`, and is assured that `g a` is a synchronous value that doesn't
    do any shifting itself.
  11. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -93,7 +93,7 @@ The type we need in Haskell can be written like so:
    ```haskell
    blocking :: Async f => (forall g. Sync g => g a) => BlockingEC => EC => f a
    ```
    the caller to `blocking` can't pass an async value like `IO[Int]` and the `g a` to `blocking`, because `blocking` requires the value
    the caller to `blocking` can't pass a potentially async value like `IO[Int]` as the `g a` to `blocking`, because `blocking` requires the value
    to be fully polymorphic in `g`, but it can pass a value defined over any `Sync g => g`.
    However, _inside_ blocking `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Sync` extends `Async`, `f` is a
    valid value. So basically we get what we want: `blocking` can call `shift`, and is assured that `g a` is a synchronous value that doesn't
  12. SystemFw revised this gist Jun 15, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -68,15 +68,15 @@ def blocking[F[_]: Async, A](p: F[A], blockingEC: ExecutionContext)(implicit ec:

    The interesting part here is that we need an `Async` constraint on `F` in order to call `shift`.

    However there's a problem: `p` is of type `F[A]` where `F` is `Async`, so there's no guarantee that `p` itself doesn't contain `shifting` calls
    However there's a problem: `p` is of type `F[A]` where `F` is `Async`, so there's no guarantee that `p` itself doesn't contain `shift`ing calls
    already, which might make `blocking` useless (since `p` might end up _not_ running on `blockingEc` as desired, given a call to `shift`
    inside `p` that moves it somewhere else).

    Note that if we change the signature of `blocking` to (note the `Sync` constraint):
    ```scala
    def blocking[F[_]: Sync, A](p: F[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A]
    ```
    We solve the problem for `p`, but can't call `shift` anymore se we can't implement it.
    We solve the problem for `p`, but can't call `shift` anymore so we can't implement it.

    This doesn't work either:
    ```scala
  13. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -68,7 +68,7 @@ def blocking[F[_]: Async, A](p: F[A], blockingEC: ExecutionContext)(implicit ec:

    The interesting part here is that we need an `Async` constraint on `F` in order to call `shift`.

    However there's a problem: `p` is of type `F[A]` where `F` is `Async`, so who guarantees that `p` itself doesn't contain `shifting` calls
    However there's a problem: `p` is of type `F[A]` where `F` is `Async`, so there's no guarantee that `p` itself doesn't contain `shifting` calls
    already, which might make `blocking` useless (since `p` might end up _not_ running on `blockingEc` as desired, given a call to `shift`
    inside `p` that moves it somewhere else).

  14. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -27,7 +27,7 @@ This pattern is informally called "shift-and-shift-back", and relies on a functi
    def shift[F[_]](ec: ExecutionContext)(implicit F: Async[F]): F[Unit] =
    F.async { cb => ... }
    ```
    What `shift` does is move whatever `F[A]` is flatMapped after to `ec`, until a subsequent call to `shift` moves it somewhere else.
    What `shift` does is move whatever `F[A]` is flatMapped afterwards to `ec`, until a subsequent call to `shift` moves it somewhere else.



  15. SystemFw revised this gist Jun 15, 2018. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -27,7 +27,8 @@ This pattern is informally called "shift-and-shift-back", and relies on a functi
    def shift[F[_]](ec: ExecutionContext)(implicit F: Async[F]): F[Unit] =
    F.async { cb => ... }
    ```
    What `shift` does is shift whatever `F[A]` is flatMapped after to `ec`, until a subsequent call to `shift` moves it somewhere else.
    What `shift` does is move whatever `F[A]` is flatMapped after to `ec`, until a subsequent call to `shift` moves it somewhere else.



    Example, say you have some code that makes a JDBC call (notoriously blocking).
  16. SystemFw revised this gist Jun 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,5 @@
    ## cats-effect
    The `cats-effect` project defines a purely functional effect type (`IO[A]`), and associated typeclass defining its behaviour.
    The `cats-effect` project defines a purely functional effect type (`IO[A]`), and associated typeclasses defining its behaviour.
    The ones we care about for this example are:

    ```scala
  17. SystemFw created this gist Jun 15, 2018.
    107 changes: 107 additions & 0 deletions RankN shift-and-shiftback.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,107 @@
    ## cats-effect
    The `cats-effect` project defines a purely functional effect type (`IO[A]`), and associated typeclass defining its behaviour.
    The ones we care about for this example are:

    ```scala
    trait Sync[F[_]] extends MonadError[F, Throwable] {
    def delay[A](a: => A): F[A]
    ...
    }

    trait Async[F[_] extends Sync[F] {
    def async[A](cb: (Either[Throwable,A] => Unit) => Unit): F[A]
    ...
    }
    ```
    which describe the ability of effects type like `IO` to embed and suspend synchronous and asynchronous side-effects

    ## Blocking, `shift`, the shift-and-shift-back pattern

    The design of `cats-effect` is oriented towards non-blocking functionality, and includes its own green-threading and "semantic blocking"
    primitives that don't actually block the underlying JVM thread. This allows users to run an arbitrary number of `IO`s on top of a CPU bound
    pool like `global`, and even on a single thread (like in scala.js). However, when interacting with legacy Java apis, one often
    deals with code that blocks JVM threads. The most common pattern to deal with this situation, after wrapping the blocking code in `F[_]: Sync`,
    is to run it on a custom thread pool that's specific to blocking code, and then run the rest of the computation back in the "normal" pool.
    This pattern is informally called "shift-and-shift-back", and relies on a function called `shift`, which can be defined for any `F[_]: Async`
    ```scala
    def shift[F[_]](ec: ExecutionContext)(implicit F: Async[F]): F[Unit] =
    F.async { cb => ... }
    ```
    What `shift` does is shift whatever `F[A]` is flatMapped after to `ec`, until a subsequent call to `shift` moves it somewhere else.


    Example, say you have some code that makes a JDBC call (notoriously blocking).

    ```scala
    def dbCall[F[_]: Sync]: F[MyType] = F.delay {... query that blocks... }
    ```
    which is then followed my normal async or cpu-bound code, like an http4s http call, or some data transformation
    ```scala
    def post[F[_]: Async](a: MyType): F[HttpResponse] = ....
    ```
    and let's say that we have an `EC` for blocking, called `blockingEC` and we run everything else on `global`.
    An example of "shift-and-shift-back" would be:
    ```scala
    def prog[F[_]: Async]: F[HttpResponse] =
    for {
    _ <- Async.shift[F](blockingEC]
    myType <- dbCall[F]
    _ <- Async.shift(global)
    res <- post[F](myType)
    } yield res
    ```

    ## A `blocking` combinator

    The above pattern works well enough, but it's a bit tedious and error-prone for the end user to have to remember to do it by hand each
    time, so let's write a combinator for it (for those familiar with cats-effect, I'm omitting `Timer` and `bracket` for simplicity):

    ```scala
    def blocking[F[_]: Async, A](p: F[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A] =
    for {
    _ <- Async.shift[F](blockingEc)
    res <- p
    _ <- Async.shift
    } yield res
    ```

    The interesting part here is that we need an `Async` constraint on `F` in order to call `shift`.

    However there's a problem: `p` is of type `F[A]` where `F` is `Async`, so who guarantees that `p` itself doesn't contain `shifting` calls
    already, which might make `blocking` useless (since `p` might end up _not_ running on `blockingEc` as desired, given a call to `shift`
    inside `p` that moves it somewhere else).

    Note that if we change the signature of `blocking` to (note the `Sync` constraint):
    ```scala
    def blocking[F[_]: Sync, A](p: F[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A]
    ```
    We solve the problem for `p`, but can't call `shift` anymore se we can't implement it.

    This doesn't work either:
    ```scala
    def blocking[F[_]: Async, G[_]: Sync A](p: G[A], blockingEC: ExecutionContext)(implicit ec: ExecutionContext): F[A]
    ```
    because we would need to `flatMap` `F` and `G` in the same for comprehension, and there's no way to unify those.

    ## RankN types
    What we need is a RankN type: we want `p` to be passed as a polymorphic value, so that it's the _callee_ (`blocking`) that decides
    which `F` is `p` instantiated to, and not the _caller_ (which is in control with normal parametric polymorphism).

    The type we need in Haskell can be written like so:

    ```haskell
    blocking :: Async f => (forall g. Sync g => g a) => BlockingEC => EC => f a
    ```
    the caller to `blocking` can't pass an async value like `IO[Int]` and the `g a` to `blocking`, because `blocking` requires the value
    to be fully polymorphic in `g`, but it can pass a value defined over any `Sync g => g`.
    However, _inside_ blocking `g a` can be instantiated to whatever `g` has a `Sync` instance, and since `Sync` extends `Async`, `f` is a
    valid value. So basically we get what we want: `blocking` can call `shift`, and is assured that `g a` is a synchronous value that doesn't
    do any shifting itself.
    Apologies for the lack of Scala in this last paragraph, but Scala doesn't support RankN types or polymorphic values, and I think this is
    a compelling example (there are ways to simulate this specific scenario I think).
    There are many other use cases though, ranging from `Free.foldMap` to the `ST monad`, to safe resource region that are enforced at compile
    time, to many more.