Skip to content

Instantly share code, notes, and snippets.

@flipsi
Last active July 24, 2020 11:11
Show Gist options
  • Select an option

  • Save flipsi/c147afd6980f13b94934ff6a1d20b54e to your computer and use it in GitHub Desktop.

Select an option

Save flipsi/c147afd6980f13b94934ff6a1d20b54e to your computer and use it in GitHub Desktop.
ZIO vs. Cats IO - Functional Programming libraries in Scala.

ZIO vs. Cats IO - Functional Programming libraries in Scala

Last updated on 2020-07-24 13:07

OKRs Q3 2020 Key result: "I have decided between ZIO or Cats IO (or similar) and justified this decision."

This document is a wrap-up of my research on which Functional Programming library to use in Scala.

Podcast: CoRecursive - Throw Away the Irrelevant with John A De Goes

https://corecursive.com/009-throw-away-the-irrelevant-with-john-a-de-goes/

(45:37)

  • Adam: "There's a whole bunch of IO monads in Scala, there's ScalaZ, there's Future, there's Monix Tasks. Is this good or bad?"
  • John: "The bad part is it's sort of painful and it creates a lot of confusion and lot's of incompatible codebases, incompatible libraries and so forth. But the good thing is, it's parallel exploration of the landscape of possiblities. And I think as painful as it might be, standardizing on one type too soon means you didn't explore the space of all possible solutions and I think in the end this hurts the ecosystem. Once you ship something in the standard library, no matter how crappy it is, it's gonna be there forever and all the libraries are gonna use it, which is a reason to not have a standard library or a reason to very carefully add things into there, because it's very hard to change it, very hard to improve it over time in response to new learnings. And I think the situation we have in Scala is, we have lots of old Monads or Monad like things (Future is Monad like) and those made so many terrible mistakes. But we've learned from mistakes and created other data types that don't have the same drawbacks. We're probably three generations in: The Future era, the ScalaZ Task era, the post-Task era ( things like Monix tasks and ScalaZ 8 IO)... and we've learned a lot in these generations. Ultimately the community is gonna benefit [...] and no doubt we're not at the end of innovation yet. Ultimately it will result in more people coming to Functional Programming for the benefits that these effects monads have, which I think is a good thing for the community in the long run, as painful as it might be in the short." (48:54)
  • Adam: "So do you think that it will be good at some point to not have ScalaZ and Cats? Or is it good to have these two competing functional standard libraries?"
  • John: "That's an interesting question. I think prior to ScalaZ 8 there's really no reason for both of them to exists, because Cats replicated and used some of the code of ScalaZ 7 - it was a subset of ScalaZ 7, but still architecturally very similar. But I think with the difference between Cats and ScalaZ 8 is going to be significant enough that it's possible they end up living alongside each other. Cats is aiming to be much smaller and minimalistic [...] and is also aiming for long-term compatibility. Cats is at 1.0 which means that by large it's not going to change which is going to make it attractive for certain companies who need a few minimal abstractions and wanna be able to stick with that for 2 or 4 or 5 years or even never have to change. ScalaZ 8 is quite different and rethinking everything that you can possibly imagine about Functional Programming in Scala. For example monad transformers in my opinion do not scale on the JVM [...]. I think what we have to do as we strive to innovate we have to figure out what it means to do FP in Scala, because it doesn't look like FP in Haskell. If we just copy more of the same stuff, it's just going to result in more people rejecting FP, because they try it and find out it doesn't actually solve the problems in a way that the business needs them to be solved. We need to get away from that mindset that FP in Scala should be exactly the same as in Haskell. That's just not true. [...] Cat it's gonna probably stick on its current path which is gonna make it useful for shops that are doing light FP and don't need a whole lot. I see ScalaZ 8 being the choice for the shops doing pure FP who also don't wanna compromise on performance."

(54:55)

  • John: "In Scala we have better type class hierarchies than Haskell, more clean, with the ScalaZ 8 IO monad has a vastly cleaner semantic for error handling and interruption than the Haskell model."

Blogpost: ZIO & Cats Effect: A Match Made in Heaven

https://degoes.net/articles/zio-cats-effect

Application developers who use Cats Effect face a far more difficult choice: which of the major effect types they will use to build their applications.

Application developers have three major choices:

  • Cats IO, the reference implementation in Cats Effect
  • Monix, with its Task data type and associated reactive machinery
  • More recently, ZIO, with its ZIO data type and concurrent machinery

In this post, I’m going to argue that if you are building a Cats Effect application, then ZIO provides a compelling choice, one with design choices and features quite different than the Cats IO reference implementation.

Without further ado, let’s take a look at my top 12 reasons why ZIO and Cats Effect are a match made in heaven!

My major take-aways

Resource-Safety for Mortals

  • The bracket operator is interruption-proof: if the acquire succeeds, then release will be called, no matter what, even if the effect that uses the resource is interrupted. Further, neither the acquire nor release can be interrupted, providing a strong guarantee of resource safety.
  • ZIO lets anyone write interruption-friendly code, operating at a high-level, with declarative, composable operators, and doesn’t force you to either choose between extreme complexity and poor performance on the one hand, and wasted resources and deadlocks on the other.
  • Cats IO chose to provide only a single operation to manage interruptibility: a combinator called uncancelable. This makes a whole region uninterruptible. However, by itself the operation is of limited use, and can easily lead to code that wastes resources or deadlocks.

Guaranteed Finalizers

  • ZIO’s ensuring operation can be used exactly like try / finally. ZIO provides the following guarantee on effect.ensuring(finalizer): If effect begins execution, then finalizer will begin execution when the effect stops execution.
  • The Cats IO data type chose a different, weaker guarantee. For effect.guarantee(finalizer), the guarantee is weakened as follows: If effect begins execution, then finalizer will begin execution when the effect stops execution, unless problematic effects are composed into effect.

Lossless Errors

  • During all operations (including cleanup for a failed or interrupted effect), ZIO aggregates errors into the Cause[E] data structure, which can be accessed at any time. As a result, ZIO never loses any errors: they can all be accessed at the value level, and then logged, inspected, or transformed, as dictated by business requirements.
  • Cats IO chose to embrace a lossy error model. Wherever ZIO would compose two errors using Cause[E], Cats IO “throws” one error away—for example, by calling e.printStackTrace() on the tossed error.

Deadlock-Free Async

Both ZIO and Cats IO provide a constructor that allows one to take callback-based code, and lift it into an effect value.

  • ZIO provides the guarantee that control is returned to the caller immediately, which can then resume execution normally.
  • On the other hand, Cats IO provides no such guarantee, which means the caller thread invoking the callback may get “stuck” waiting indefinitely for control to be returned to it.

Precise Future Interop

Dealing with Scala’s Future is a reality for many code bases.

  • ZIO ships with a fromFuture method that provides a ready-made execution context.
  • Cats IO chose to accept a Future that has already been constructed with an external ExecutionContext. Since one can always choose to ignore the provided ExecutionContext, the ZIO choice can be seen as a strict generalization of Cats IO capabilities, providing more seamless and precise interop with Future in the common case, but not preventing exceptions to the rule.

Beginner-Friendly

  • ZIO has made many decisions to increase usability for new users, without cutting corners or sacrificing principles for advanced users. For example:
    • Jargon-free naming (e.g. ZIO.succeed instead of Applicative[F].pure)
    • No use of higher-kinded types or type classes (Scalaz instances available in optional modules)
    • Auto-complete-friendly naming that groups similar methods by prefix (e.g. zip / zipPar)
    • Concrete methods on concrete data types, which aids discoverability and traversability
    • Conversion from all Scala data types to the ZIO effect type (e.g. ZIO.fromFuture)
    • Full, out-of-the-box type inference for all data types and methods
  • Cats IO chose to delegate most functionality, names, and decisions around type-inference to Cats. This keeps the reference implementation small, but may increase ramp-up time for developers new to functional programming.

Summary

Cats Effect has done great things for the Scala ecosystem, providing a growing roster of libraries that all work together. Application developers who are using Cats Effect libraries now face the difficult decision of choosing which of the major effect types to use with Cats Effect libraries: Cats IO, Monix, or ZIO. While different people will make different choices that are uniquely suited for them, if you value some of the design decisions described in this post, then I hope you will find that together, ZIO and Cats Effect make a killer combination!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment