Skip to content

Problems inferring correct type for extension methods #11413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
justinhj opened this issue Feb 15, 2021 · 6 comments
Closed

Problems inferring correct type for extension methods #11413

justinhj opened this issue Feb 15, 2021 · 6 comments

Comments

@justinhj
Copy link

justinhj commented Feb 15, 2021

Compiler version

3.0.0-RC1

Minimized code

object Minimized extends App {
  
  trait Monad[F[_]]:
    def pure[A](a: A): F[A]
    
    extension[A,B](fa :F[A]) 
      def map(f: A => B): F[B] = fa.flatMap(a => pure(f(a)))
      def flatMap(f :A=>F[B]):F[B]
  end Monad

  // Instances of monad
 // note that if you comment out eitherMonad the code will compile, even though the Either type is never used 
  given eitherMonad[Err]: Monad[[X] =>> Either[Err,X]] with
    def pure[A](a: A): Either[Err, A] = Right(a)
    extension [A,B](x: Either[Err,A]) def flatMap(f: A => Either[Err, B]) = {
      x match {
        case Right(a) => f(a)
        case Left(err) => Left(err)
      }
    }

  given optionMonad: Monad[Option] with
    def pure[A](a: A) = Some(a)
    extension[A,B](fa: Option[A])
      def flatMap(f: A => Option[B]) = {
        fa match {
          case Some(a) =>
            f(a)
          case None =>
            None
        }
      }

  case class Transformer[F[_]: Monad,A](val wrapped: F[A])

  given transformerMonad[F[_]: Monad]: Monad[[X] =>> Transformer[F,X]] with {

    def pure[A](a: A): Transformer[F,A] = Transformer(summon[Monad[F]].pure(a))

    extension [A,B](fa: Transformer[F,A])
      def flatMap(f: A => Transformer[F,B]) = {
        val ffa: F[B] = summon[Monad[F]].flatMap(fa.wrapped)(a => f(a).wrapped)
        Transformer(ffa)
      }
  }
  
  type TransformerOption[A] = Transformer[Option, A]
  
  val pure10 = summon[Monad[TransformerOption]].pure(10)
  val fm = pure10.flatMap(a => Transformer(Option(a + 1))) // COMPILE ERROR HERE
  println(fm)
}

Output

Minimized.scala:49:19
value flatMap is not a member of Minimized.Transformer[Option, Int].
An extension method was tried, but could not be fully constructed:

    Minimized.transformerMonad[F](
      /* ambiguous: both given instance eitherMonad in object Minimized and object optionMonad in object Minimized match type Minimized.Monad[F] */
        summon[Minimized.Monad[F]]
    ).flatMap()
  val fm = pure10.flatMap(a => Transformer(Option(a + 1)))

Expectation

Code should compile with no ambiguity

@liufengyun liufengyun added area:typer stat:needs minimization Needs a self contained minimization area:extension-methods and removed stat:needs minimization Needs a self contained minimization labels Feb 16, 2021
@justinhj
Copy link
Author

justinhj commented Feb 28, 2021

To add some more information and context here. I can make the code work by adding the following implicit class conversion. It seems like this is a bit messy and would cause confusion to the users and I can't explain why I need it other than to help the type.

implicit final class TransformerOps[F[_]: Monad, A](private val fa: Transformer[F,A]) {
    def flatMap[B](f: A => Transformer[F,B]): Transformer[F,B] =
      Monad[[A] =>> Transformer[F,A]].flatMap(fa)(a => f(a))

    def map[B](f: A => B): Transformer[F,B] =
      Monad[[A] =>> Transformer[F,A]].map(fa)(a => f(a))
  }

It makes me wonder though if my issue is a typer bug, if I am needing a fix like this one...

Kinda-Curried Type Parameters

https://tpolecat.github.io/2015/07/30/infer.html

It seems to me that the very same code in Scala 2 style (which also needs the implicit Ops conversion class) should translate to Scala 3 and not need it.

Anyway, hope this helps bring clarity to the issue.

@smarter
Copy link
Member

smarter commented Feb 28, 2021

It would be really helpful for us if you could reduce your code example to the minimal amount of code needed to reproduce the issue: it doesn't have to make sense, just to demonstrate that something isn't inferred when it could.

@smarter smarter added the stat:needs minimization Needs a self contained minimization label Feb 28, 2021
@justinhj
Copy link
Author

Sounds good. I have removed a significant amount of code.

@justinhj
Copy link
Author

justinhj commented Mar 9, 2021

Just an update, it definitely isn't related to the "kinda-curried" type parameters link above. So I'm still not really sure if I should be expecting this to type check or not. It seems to me that it should be able to construct the extension method without the ambiguity it is seeing.

@prolativ
Copy link
Contributor

Here

pure10.flatMap(a => Transformer(Option(a + 1)))

expands to

transformerMonad.flatMap(pure10)(a => Transformer(Option(a + 1)))

and at this point compiler is confused while at the point of evaluating transformerMonad both transformerMonad[Option] and transformerMonad[Either] make sense (the compiler doesn't know yet that you're in the context of Option rather than Either) so it's not clear which implicit should be taken.
You can try adding some kind of extension proxy to make this to make your snippet compile

extension[F[_], A](fa: F[A])(using monad: Monad[F])
  def flatMap[B](f: A => F[B]) = monad.flatMap(fa)(f)

@justinhj
Copy link
Author

Thanks for the help, so it seems like this isn't a bug and can be closed.

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

No branches or pull requests

4 participants