• This repository has been archived on 27/Feb/2021
  • Stars
    star
    174
  • Rank 214,114 (Top 5 %)
  • Language
    Scala
  • License
    Apache License 2.0
  • Created over 8 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Seamless interop layer between cats and scalaz

shims Gitter Latest version

As of Cats 2.3.0 (and above), most major instances are included in the implicit scope without requiring extra imports. This is tremendously convenient for users, but it fundamentally breaks Shims, since any scope in which shims is imported along with instances from scalaz results in an unprioritized ambiguity. However, no one really complained about this, despite it being broken for months, which leads me to conclude that this library is no longer needed and may be archived.

This repository is left up for pedagogical reasons, as it is quite interesting to see what techniques are necessary to do something like this in Scala's type system (both Scala 2 and Scala 3). However, I will no longer be maintaining Shims going forward. I recommend everyone who was depending on it to upgrade to Cats at your earliest convenience, as it will provide a generally better experience all around within a more modern ecosystem.

Shims aims to provide a convenient, bidirectional, and transparent set of conversions between scalaz and cats, covering typeclasses (e.g. Monad) and data types (e.g. \/). By that I mean, with shims, anything that has a cats.Functor instance also has a scalaz.Functor instance, and vice versa. Additionally, every convertible scalaz datatype โ€“ such as scalaz.State โ€“ has an implicitly-added asCats function, while every convertible cats datatype โ€“ such as cats.free.Free โ€“ has an implicitly-added asScalaz function.

Only a single import is required to enable any and all functionality:

import shims._

Toss that at the top of any files which need to work with APIs written in terms of both frameworks, and everything should behave seamlessly. You can see some examples of this in the test suite, where we run the cats laws-based property tests on scalaz instances of various typeclasses.

Usage

Add the following to your SBT configuration:

libraryDependencies += "com.codecommit" %%% "shims" % "<version>"

Cross-builds are available for Scala 2.12 and 2.13, and Dotty 0.25.0 and 0.26.0-RC1. ScalaJS builds target the 1.x line. It is strongly recommended that you enable the relevant SI-2712 fix in your build if using 2.12. Details here. A large number of conversions will simply not work without partial unification.

Note that shims generally follows epoch.major.minor versioning schemes, meaning that changes in the second component may be breaking. This is mostly because maintaining strict semver with shims would be immensely difficult due to the way the conversions interact. Shims is more of a leaf-level project, anyway, so semantic versioning is somewhat less critical here. Feel free to open an issue and make your case if you disagree, though.

Once you have the dependency installed, simply add the following import to any scopes which require cats-scalaz interop:

import shims._

That's it!

Effect Types

You can also use shims to bridge the gap between the older scalaz Task hierarchy and newer frameworks which assume cats-effect typeclasses and similar:

libraryDependencies += "com.codecommit" %% "shims-effect" % "<version>"
import shims.effect._

For more information, see the shims-effect subproject readme.

Upstream Dependencies

  • cats 2.0.0
  • scalaz 7.2.28

At present, there is no complex build matrix of craziness to provide support for other major versions of each library. This will probably come in time, when I've become sad and jaded, and possibly when I have received a pull request for it.

Quick Example

In this example, we build a data structure using both scalaz's IList and cats' Eval, and then we use the cats Traverse implicit syntax, which necessitates performing multiple transparent conversions. Then, at the end, we convert the cats Eval into a scalaz Trampoline using the explicit asScalaz converter.

import shims._

import cats.Eval
import cats.syntax.traverse._
import scalaz.{IList, Trampoline}

val example: IList[Eval[Int]] = IList(Eval.now(1), Eval.now(2), Eval.now(3))

val sequenced: Eval[IList[Int]] = example.sequence
val converted: Trampoline[IList[Int]] = sequenced.asScalaz

Conversions

Typeclasses

Typeclass conversions are transparent, meaning that they will materialize fully implicitly without any syntactic interaction. Effectively, this means that all cats monads are scalaz monads and vice versa.

What follows is an alphabetized list (in terms of cats types) of typeclasses which are bidirectionally converted. In all cases except where noted, the conversion is exactly as trivial as it seems.

  • Alternative
    • Note that MonadPlus doesn't exist in Cats. I'm not sure if this is an oversight. At present, no conversions are attempted, even when Alternative and FlatMap are present for a given F[_]. Change my mind.
  • Applicative
  • Apply
  • Arrow
  • Choice
    • Requires a Bifunctor[F] in addition to a Choice[F]. This is because scalaz produces a A \/ B, while cats produces an Either[A, B].
  • Bifoldable
  • Bifunctor
  • Bitraverse
  • Category
  • Choice
  • CoflatMap
  • Comonad
  • Compose
  • Contravariant
  • Distributive
  • Eq
  • FlatMap
    • Requires Bind[F] and either BindRec[F] or Applicative[F]. This is because the cats equivalent of scalaz.Bind is actually scalaz.BindRec. If an instance of BindRec is visible, it will be used to implement the tailRecM function. Otherwise, a stack-unsafe tailRecM will be implemented in terms of flatMap and point.
    • The cats โ†’ scalaz conversion materializes scalaz.BindRec; there is no conversion which just materializes Bind.
  • Foldable
  • Functor
  • InjectK
    • This conversion is weird, because we can materialize a cats.InjectK given a scalaz.Inject, but we cannot go in the other direction because scalaz.Inject is sealed.
  • Invariant (functor)
  • Monad
    • Requires Monad[F] and optionally BindRec[F]. Similar to FlatMap, this is because cats.Monad constrains F to define a tailRecM function, which may or may not be available on an arbitrary scalaz.Monad. If BindRec[F] is available, it will be used to implement tailRecM. Otherwise, a stack-unsafe tailRecM will be implemented in terms of flatMap and point.
    • The cats โ†’ scalaz conversion materializes scalaz.Monad[F] with scalaz.BindRec[F], reflecting the fact that cats provides a tailRecM.
  • MonadError
    • Similar requirements to Monad
  • Monoid
  • MonoidK
  • Order
  • Profunctor
  • Representable
  • Semigroup
  • SemigroupK
  • Show
  • Strong
  • Traverse

Note that some typeclasses exist in one framework but not in the other (e.g. Group in cats, or Split in scalaz). In these cases, no conversion is attempted, though practical conversion may be achieved through more specific instances (e.g. Arrow is a subtype of Split, and Arrow will convert).

And don't get me started on the whole Bind vs BindRec mess. I make no excuses for that conversion. Just trying to make things work as reasonably as possible, given the constraints of the upstream frameworks.

Let me know if I missed anything! Comprehensive lists of typeclasses in either framework are hard to come by.

Datatypes

Datatype conversions are explicit, meaning that users must insert syntax which triggers the conversion. In other words, there is no implicit coercion between data types: a method call is required. For example, converting between scalaz.Free and cats.free.Free is done via the following:

val f1: scalaz.Free[F, A] = ???
val f2: cats.free.Free[F, A] = f1.asCats
val f3: scalaz.Free[F, A] = f2.asScalaz
Cats Direction Scalaz
cats.Eval ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.Free.Trampoline
cats.Eval ๐Ÿ‘ˆ scalaz.Name
cats.Eval ๐Ÿ‘ˆ scalaz.Need
cats.Eval ๐Ÿ‘ˆ scalaz.Value
cats.arrow.FunctionK ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.~>
cats.data.Cokleisli ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.Cokleisli
cats.data.Const ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.Const
cats.data.EitherK ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.Coproduct
cats.data.EitherT ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.EitherT
cats.data.IndexedStateT ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.IndexedStateT
cats.data.Ior ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.\&/
cats.data.Kleisli ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.Kleisli
cats.data.NonEmptyList ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.NonEmptyList
cats.data.OneAnd ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.OneAnd
cats.data.OptionT ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.OptionT
cats.data.OptionT ๐Ÿ‘ˆ scalaz.MaybeT
cats.data.RWST ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.RWST
cats.data.Validated ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.Validation
cats.data.ValidatedNel ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.ValidationNel
cats.data.WriterT ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.WriterT
cats.free.Free ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.Free
scala.Option ๐Ÿ‘ˆ scalaz.Maybe
scala.util.Either ๐Ÿ‘ˆ๐Ÿ‘‰ scalaz.\/

Note that the asScalaz/asCats mechanism is open and extensible. To enable support for converting some type "cats type" A to an equivalent "scalaz type" B, define an implicit instance of type shims.conversions.AsScalaz[A, B]. Similarly, for some "scalaz type" A to an equivalent "cats type" B, define an implicit instance of type shims.conversions.AsCats[A, B]. Thus, a pair of types, A and B, for which a bijection exists would have a single implicit instance extending AsScalaz[A, B] with AsCats[B, A] (though the machinery does not require this is handled with a single instance; the ambiguity resolution here is pretty straightforward).

Wherever extra constraints are required (e.g. the various StateT conversions require a Monad[F]), the converters require the cats variant of the constraint. This should be invisible under normal circumstances since shims itself will materialize the other variant if one is available.

Nesting

At present, the asScalaz/asCats mechanism does not recursively convert nested structures. This situation most commonly occurs with monad transformer stacks. For example:

val stuff: EitherT[OptionT[Foo, *], Errs, Int] = ???

stuff.asCats

The type of the final line is cats.data.EitherT[scalaz.OptionT[Foo, *], Errs, Int], whereas you might expect that it would be cats.data.EitherT[cats.data.OptionT[Foo, *], Errs, Int]. It is technically possible to apply conversions in depth, though it require some extra functor constraints in places. The primary reason why this isn't done (now) is compile time performance, which would be adversely affected by the non-trivial inductive solution space.

It shouldn't be too much of a hindrance in any case, since the typeclass instances for the nested type will be materialized for both scalaz and cats, and so it doesn't matter as much exactly which nominal structure is in use. It would really only matter if you had a function which explicitly expected one thing or another.

The only exception to this rule is ValidationNel in scalaz and ValidatedNel in cats. Converting this composite type is a very common use case, and thus an specialized converter is defined:

val v: ValidationNel[Errs, Int] = ???

v.asCats   // => v2: ValidatedNel[Errs, Int]

Note that the scalaz.NonEmptyList within the Validation was converted to a cats.data.NonEmptyList within the resulting Validated.

In other words, under normal circumstances you will need to manually map nested structures in order to deeply convert them, but ValidationNel/ValidatedNel will Just Workโ„ข without any explicit induction.

Contributors

None of this would have been possible without some really invaluable assistance:

  • Guillaume Martres (@smarter), who provided the key insight into the scalac bug which was preventing the implementation of Capture (and thus, bidirectional conversions)
  • Christopher Davenport (@ChristopherDavenport), who contributed the bulk of shims-effect in its original form on scalaz-task-effect

More Repositories

1

gll-combinators

A parser combinator library based on the GLL algorithm
Scala
298
star
2

emm

A general monad for managing stacking effects
Scala
205
star
3

parseback

A Scala implementation of parsing with derivatives
Scala
196
star
4

anti-xml

The scala.xml library has some very annoying issues. Time for a clean-room replacement!
Scala
169
star
5

sbt-github-packages

A simple sbt plugin for publishing to GitHub Packages, in the style of sbt-sonatype and sbt-bintray
Scala
169
star
6

cccp

Common Colaborative Coding Protocol
Scala
119
star
7

extreme-cleverness

A set of functional collections created, ported and modified for my tak at NE Scala 2011
Scala
91
star
8

sparse

Generalized, incremental parser combinators for scalaz-stream
Scala
63
star
9

skolems

A microlibrary for Scala encodings of higher-rank quantifiers
Scala
60
star
10

derivative-combinators

An implementation of derivative parsing in the parser combinator framework
Scala
59
star
11

sbt-spiewak

A plugin which represents my personal SBT project baseline
Scala
55
star
12

kitteh-redis

A toy Redis server implemented using pure FP on top of Cats Effect, Fs2, and Scodec
Scala
42
star
13

jedit-modes

A collection of new and modified edit modes for jEdit
34
star
14

scala-bison

A recursive ascent/descent parser generator for Scala
Scala
33
star
15

smock

A utility harness for testing free programs (built on specs2)
Scala
29
star
16

scala-collections

A number of collection implementations for Scala (including a few ported from Clojure)
Scala
27
star
17

async-runtime-benchmarks

Comparative benchmarks between asynchronous runtimes
Scala
27
star
18

scala-stm-proto

An implementation of a software transactional memory framework in Scala
Scala
19
star
19

ensime-sidekick

An ENSIME SideKick plugin for jEdit
Scala
16
star
20

sillio

Scala
14
star
21

linguistic-programming

Sources for my presentation on crafting DSLs in Scala
Scala
10
star
22

activeobjects

A Git fork of the mainline ActiveObjects SVN
Java
10
star
23

base.g8

My cool template
Scala
8
star
24

jbencode

A very fast Java Bencode pull parser/generator
Java
7
star
25

sbt-evil-mode

๐Ÿ˜ˆ
Scala
7
star
26

shapely

Scala
7
star
27

cont-exp

Experiments with the continuation monad
Scala
7
star
28

dbpool

A fork of the excellent DBPool connection pool
Java
5
star
29

wronglisp

Scala
4
star
30

rst2twiki

A converter script from ReStructured Text to TWiki markup
4
star
31

iota-instances

Scalaz instances and things for iotaz coproducts
Scala
3
star
32

parser-workshop

Parser workshop at flatMap(Oslo) 2013
Scala
3
star
33

dist-mapper

A non-functional key/value store backend
Scala
2
star
34

schrodinger

Common communication protocol for IO / Task / Future data types
Scala
2
star
35

jedit-macros

1
star