There are a lot of cases where things simply differ by name. These are the easiest changes to make.
scalaz.Zip
->cats.Cartesian
(with the primary operationzip
->product
)scalaz.Unzip
->cats.MonadCombine
... sort ofscalaz.Task
->fs2.Task
(ormonix.Task
)
Additionally, some Scalaz data types are gone, in favor of using the original Scala types. E.g.
\/
->Either
(and-\/
->Left
,\/-
->Right
)IList
->List
IMap
->Map
ISet
->Set
Maybe
->Option
The use of a: => A
for laziness has been replaced with a: cats.Eval[A]
, which is overall much more flexible, but puts a bit more work on the caller. E.g., what was _.foldRight(1)(_ + _)
in Scalaz looks like _.foldRight(now(1))((e, a) => a.map(e + _)).value
in Cats.
- missing default implementations for
foldLeft
andfoldRight
(for efficiency reasons)
Scalaz uses type tags like Int @@ Max
to select specific instances when multiple are available. This is handled differently in the Typelevel world. The Newts library provides wrappers like Max[A]
for the same purpose, with a Newtype
case class that provides wrap
and unwrap
methods.
The unwrap
method has the same name in Cats and Scalaz, but the wrapping is different (e.g., Scalaz’s .disjunction
vs Newts’ .asAny
).
This is a nice option when your usage of Scalaz falls into its patterns -- basically, only relying on type classes and potentially providing instances for your type classes on Scalaz types. This allows you to abstract over the type classes, and then push the instances down into mylibrary-scalaz
and mylibrary-cats
subprojects.
This is probably the easiest to manage if it fits you usage.
Yax is like a Scala preprocessor. It allows you to indicate which blocks of code should be used with which library.
To make it less intrusive, I recommend renaming imports as possible:
#+scalaz
import scalaz._
#-scalaz
#+cats
import cats._
import cats.kernel.{Eq => Equal}
import scala.{Either => \/}
#-cats
This reduces the amount of changes that need to happen in the code. Additionally, choose shared method names if possible. For example, Scalaz calls monadic η point
, but Cats calls it pure
. However, Scalaz also provides pure
as an alias, so if you use that name consistently, you can avoid conditionalizing those methods.
A list:
Applicative.pure
instead of.point
===
instead of≟
This is useful if you transitioning from one to the other, and likely only have a fairly short period of supporting both.
The hardest part here is keeping both branches in sync with new changes. It's helpful to minimize the changes so that merging between them has fewer (and more obviously managed) conflicts.
Similar to the Yax approach, you can largely do import renaming then restrict in-code changes to different method names.
In general, as long as you're supporting both, you want to avoid making too many stylistic changes that don't map well between the two libraries.