Git Product home page Git Product logo

shims's Introduction

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

shims's People

Contributors

alexandru avatar djspiewak avatar edmundnoble avatar jorokr21 avatar mikla avatar scala-steward avatar xuwei-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shims's Issues

Diverging implicit expansion even when implicits present

The following fails to compile:

import shims._

import cats._
import cats.instances.list._

implicit val eqStr: Eq[String] = null  // rather than importing

Eq[String]        // diverging implicit expansion
Eq[List[String]]  // diverging implicit expansion

The first-order version actually works just fine sometimes. The second-order version basically always fails. Both versions always work fine if we use Order rather than Eq, so this is a prioritization problem. I'm not sure exactly why it's happening, but obviously it's a blocker for using shims in a real codebase.

Equal[Option[A]] ambiguity with Cats 2.2.0

Scastie. With Shims 2.2.0 and Cats 2.2.0, we get this:

import scalaz.Scalaz._
import shims.eqToScalaz

implicitly[scalaz.Equal[Option[Short]]]
//ambiguous implicit values:
// both method optionOrder in trait OptionInstances of type [A](implicit A0: scalaz.Order[A])scalaz.Order[Option[A]]
// and method eqToScalaz in trait EqConversions of type [A](implicit AC: shims.util.Capture[cats.kernel.Eq[A]])scalaz.Equal[A] with shims.conversions.Synthetic
// match expected type scalaz.Equal[Option[Short]]
//could not find implicit value for parameter e: scalaz.Equal[Option[Short]]
//not enough arguments for method implicitly: (implicit e: scalaz.Equal[Option[Short]])scalaz.Equal[Option[Short]].
//Unspecified value parameter e.

Doesn't build on 2.10

Apparently something about type negation or refinement solutions (or some combination of the two) doesn't work well on 2.10. This is the origin of the build failures.

way to erase the unwanted namespace?

First, this is amazing, thank you so much!

My first usecase for shims didn't go so well. My work project uses cats and we often find ourselves wanting to use scalaz to get extra features or workaround bugs in cats (usually lazy evaluation bugs). I wanted to be able to use shims to write one function in scalaz and then convert it to our "public api" (cats). However, since the file already had the import cats._ stuff, I was getting this sort of thing

type mismatch;
[error]  found   : F[Option[A]]
[error]  required: ?{def map: ?}
[error] Note that implicit conversions are not applicable because they are ambiguous:
[error]  both method toFunctorOps in trait ToFunctorOps of type [F[_], A](target: F[A])(implicit tc: cats.Functor[F])cats.Functor.Ops[F,A]{type TypeClassType = cats.Functor[F]}
[error]  and method ToFunctorOps in trait ToFunctorOps of type [F[_], A](v: F[A])(implicit F0: scalaz.Functor[F])scalaz.syntax.FunctorOps[F,A]
[error]  are possible conversion functions from F[Option[A]] to ?{def map: ?}
[error]           e.toOption.run.map(_.toStream)
[error]                      ^

The workaround appears to be that I need to put my scalaz function in a separate file.

But wouldn't it be nice if there was a way of removing the cats imports in a local scope? Do you think that would be possible? Probably needs a compiler plugin.

LiftIO for Sync is incorrect

but don't feel so bad. I fell for it too, and it took me a couple of attempts.

The trouble is that using LiftIO will lift the wrong thing when you have an EitherT in there, plus you can get into all kinds of horrible infinite loops because Sync extends MonadError and typeclass incoherence ensues...

The correct solution is to provide a custom instance for each thing, and not rely on the MonadIO. My variant (but for ioeffect) is here https://gitlab.com/fommil/upgrade-typelevel-to-scalaz/blob/master/src/main/scala/ScalazCats.scala#L172-272

(this is not cats-effect's Sync, but one adapted to scalaz to allow upgrading cats to scalaz).

Finally, incase you weren't aware, scalaz-ioeffect already provides an Effect instance.

cats `show` interpolator doesn't work

Using a scalaz.Show instance with cats' show interpolator results in a type mismatch as a result of a failed implicit conversion A => Shown, presumably because of implicit chaining issues.

ScalaJS artifacts

Apparently the ScalaJS artifacts have not been made available for version 1.0-xxx.

Explore guided conversion

Right now, we're doing a direct conversion (e.g. scalaz.Functor to cats.Functor) which is how we get cycles, but there's another option: indirect conversion. This is pretty close to the asScalaz/asCats stuff, except fully implicit, and I think due to the fact that it's a single function, it won't trigger cyclic issues.

Add cats-effect instances

So apparently we really do need to pull these in here. @ChristopherDavenport was right! The issue is that cats.effect.Effect extends cats.Monad, which in turn makes shims.MonadConversions ambiguous. So… that's annoying. And it's a very common use-case.

The only way to resolve this is to have a shims-effect subproject which re-cakes the shims package object, providing Effect[Task] and stuff like that. This ensures that the implicit priority is correct, which isn't something that can otherwise be done externally (since shims is brought in via an import).

Dotty support

Requires removing the macro (which only exists to work around bugs in NSC). Shouldn't actually be all that hard to do.

Add instance lifters for scalaz equivalents

cats-effect provides instances for things like Sync[Kleisli[F, R, ?]] where Sync[F]. We should try to do the same for Scalaz as much as possible. Stack-safety might get in the way, but at least we can get some basic stuff like LiftIO.

README, State, and scope

The lead paragraph of the readme mentions State, whereas the features section explicitly declares State out of scope. A framework could utilize scalaz.State as a data type and shims.Monad as a type class, but this presents different and significant irritations for both Scalaz and Cats users downstream. I'm guessing one part or the other of the readme is wrong, and that it's the first, but I defer to the author.

Cannot get Monad conversion to work

Seem to work well for concrete types such as cats.Monad[Maybe], but it is impossible to get it working within a parametrized function.

Imports:

import scalaz._, Scalaz._, shims._
scala> def blah[F[_]](implicit F: Monad[F]): F[Unit] =
     | implicitly[cats.Monad[F]].pure(())

<console>:21: error: could not find implicit value for parameter e: cats.Monad[F]
       implicitly[cats.Monad[F]].pure(())
                 ^

If I explicitly call the conversion to get a better error message:

scala> def blah[F[_]](implicit F: Monad[F]): F[Unit] =
     | monadToCats[F].pure(())

<console>:21: error: could not find implicit value for parameter OFC: shims.util.OptionCapture[scalaz.BindRec[F]]
       monadToCats[F].pure(())
                  ^

I don't get it why it fails due to an optional constraint, but if I try to provide it obviously it fails with ambiguous implicits error:

scala> def blah[F[_]](implicit F: Monad[F], BR: BindRec[F]): F[Unit] =
     | monadToCats[F].pure(())

<console>:21: error: ambiguous implicit values:
 both value BR of type scalaz.BindRec[F]
 and value F of type scalaz.Monad[F]
 match expected type scalaz.Bind[F]
       monadToCats[F].pure(())
                          ^

<console>:21: error: could not find implicit value for parameter OFC: shims.util.OptionCapture[scalaz.BindRec[F]]
       monadToCats[F].pure(())
                  ^

Is there any workaround for this issue?

Thanks.

Cats Effect Instances

My understanding is this library works to establish a bridge between scalaz and cats. Cats-effect typeclasses are now being used in parametric coding for quite a few tools.

scalaz-task-effect exports a valid Effect instance for scalaz.concurrent.Task, that I would like to make sure is available to help this bridge.

Would this library also like to house the cats-effect instances for things like scalaz.concurrent.Task, scalaz.effect.IO etc?

It may be that nothing more is required than the above library, as it sounds like the new IO will be exporting its own instances. Just making sure to reach out to optimize the effectiveness of available tools.

Parallel Instance for Task seems plausible

I understand what is stated in the README and I totally agree. I've had this contribution to scalaz-task-effect.

ChristopherDavenport/scalaz-task-effect#46

I've been trying to push people over to this repository, however that is a slow process. So I wanted to bring this over and make it known, and possibly use over here.

I've asked to validate the Applicative instance, but if the Applicative of Parrallel task checks, it seems it would have a place.

missing Foldable

I might submit a PR myself once I start using shims, but the project I’m planning to shim needs Foldable.

Should Effect[Task].async catch exceptions thrown by the callback registry?

It looks like scalaz.concurrent.Task is catching less exceptions than cats.effect.IO. Compare:
https://github.com/typelevel/cats-effect/blob/master/core/shared/src/main/scala/cats/effect/IO.scala#L1058
with:
https://github.com/scalaz/scalaz/blob/series/7.3.x/concurrent/src/main/scala/scalaz/concurrent/Future.scala#L395

This is not really specified in a law but could be unexpected for users. I noticed that while using the default Async.shift[Task] which can throw a RejectedExecutionException by the underlying ExecutionContext.

Monad[Id] is ambiguous

To reproduce:

import scalaz._, Scalaz._, shims._
Monad[Id]

Result:

<console>:26: error: ambiguous implicit values:
 both method flatMapToScalaz in trait FlatMapConversions of type [F[_]](implicit FC: shims.util.Capture[cats.FlatMap[F]])scalaz.BindRec[F] with shims.conversions.Synthetic
 and value idInstance in package scalaz of type => scalaz.Traverse1[scalaz.Id.Id] with scalaz.Monad[scalaz.Id.Id] with scalaz.BindRec[scalaz.Id.Id] with scalaz.Comonad[scalaz.Id.Id] with scalaz.Distributive[scalaz.Id.Id] with scalaz.Zip[scalaz.Id.Id] with scalaz.Unzip[scalaz.Id.Id] with scalaz.Align[scalaz.Id.Id] with scalaz.Cozip[scalaz.Id.Id] with scalaz.Optional[scalaz.Id.Id]
 match expected type scalaz.BindRec[[X]X]
       val res0 =

First encountered on shims 1.1, upgraded to shims 1.2.1 where the problem persisted.
Scalaz version 7.2.17, no cats imports.

EDIT: Worked around by shadowing idInstance in the scope where it's used. Perhaps shims is providing the instance itself somehow?

showToScalaz can create ambiguities

If you have a cats.Show[A] and a cats.Show[B] where A <: B, then you attempt to materialize a Show[A] in some other scope where you have shims._, you can actually get ambiguities caused (I think) by showToScalaz taking a ContravariantShow instead of just a Show. This should be corrected.

Make http4s happy

Uh… stuff.

  • Right-projected Either pimp
  • KleisliLike
  • NEL??
  • Monoid
  • Instance providing (nice to have!)
    • Show
    • Order
    • Equal

Attn @rossabaker

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.