djspiewak / shims

Seamless interop layer between cats and scalaz
Apache License 2.0
174 stars 15 forks source link

Monad[Id] is ambiguous #20

Open edmundnoble opened 6 years ago

edmundnoble commented 6 years ago

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?

djspiewak commented 6 years ago

Interesting. Shims isn't providing the instance itself, but it's hard to say for sure. Id is basically just a terrible type to be using in these sorts of cases because of aliasing issues in scalac, but it definitely shouldn't be broken by shims. I'll take a look.

djspiewak commented 6 years ago

Interesting that BindRec[Id] materializes just fine…

djspiewak commented 6 years ago
scala> import scalaz._, Scalaz._, shims._
import scalaz._, Scalaz._, shims._
<console>:11: warning: Unused import
       import scalaz._, Scalaz._, shims._
                               ^
<console>:11: warning: Unused import
       import scalaz._, Scalaz._, shims._
                                        ^
import scalaz._
import Scalaz._
import shims._

scala> Monad[Id]
Monad[Id]
<console>:20: 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[scalaz.Scalaz.Id]
       val res0 =
           ^

scala> Monad[Lambda[X => X]]
Monad[Lambda[X => X]]
<console>:13: warning: Unused import
       import Scalaz._
                     ^
res1: scalaz.Monad[[X]X] = scalaz.IdInstances$$anon$1@4037c5b8

It's an aliasing issue.

djspiewak commented 6 years ago

It appears that some witchcraft is summoning cats.catsInstancesForId despite not having been imported. I really don't know what could be causing this other than a bug in scalac.

djspiewak commented 6 years ago

I figured it out. Both scalaz and cats Id type gets automagically summoned instances. Behold:

scalaz.Monad[scalaz.Id.Id]
cats.Monad[cats.Id]

Both those lines compile with no imports. So this is why things are ambiguous: both monads are in scope, and shims makes them equivalent.

But, "how?!" you ask. Good question, intrepid reader! This gist shows a condensed example of the phenomenon in play here: https://gist.github.com/8680f463d4e2e4a8aa8a9e0bb7999ce0 The implicit scope for resolving a given type includes all companions of components of the type. As it turns out, one of the components of a type is the package itself, and the package object is considered part of the package scope. As you may recall from Shapeless's Poly, objects are their own companions, and package objects are objects (mostly). So in other words, the fact that both scalaz and cats put their idInstance implicits into the package prevents us from eliminating those instances from scope.

I would consider this to be a pretty significant design flaw in both frameworks, but in fairness, this is a really really obscure corner of implicit scoping.

In the meantime, I'm not really sure if there's anything shims can do here. In theory, I'd really like shims implicits to be "magically lowest" priority in that they are only selected when no other implicits are applicable, but I don't have a handy trick for achieving that.