Search code examples
scalascala-macroshigher-kinded-typesscala-reflect

How to obtain a tree for a higher-kinded type parameter in a scala macro


I'm trying to write a macro to simplify some monad-related code (I'm using cats 1.6.0 for the Monads). For now I just want to be able to write lift[F](a) where F is a unary type constructor, and have that expand to a.pure[F]. Seems simple enough, but I can't get it working.

For now I have this code to help with type inference:

object Macros {
  class LiftPartiallyApplied[F[_]] {
    def apply[A](a: A): F[A] = macro MacroImpl.liftImpl[F, A]
  }

  def lift[F[_]] = new LiftPartiallyApplied[F]
}

And for the actual implementation of the macro:

object MacroImpl {
  def liftImpl[F[_], A](c: blackbox.Context)(a: c.Tree)(implicit tt: c.WeakTypeTag[F[_]]): c.Tree = {
    import c.universe._
    q"$a.pure[${tt.tpe.typeConstructor}]"
  }
}

Now I can call the macro like this lift[List](42) and it'll expand to 42.pure[List], great. But when I call it with a more complicated type, like lift[({type F[A] = Either[String, A]})#F](42), It'll expand to 42.pure[Either], which is obviously broken, because Either is a binary type constructor and not a unary one. The problem is I just don't know what to put there instead of ${tt.tpe.typeConstructor}

// edit: since people apparently have trouble reproducing the problem, I've made a complete repository: https://github.com/mberndt123/macro-experiment I will now try to figure out what the difference between Dmytro's and my own project is.


Solution

  • Don't put Main and Macros to the same compilation unit.


    But when I call it with a more complicated type, like lift[({type F[A] = Either[String, A]})#F](42), It'll expand to 42.pure[Either]

    Can't reproduce.

    For me lift[List](42) produces (with scalacOptions += "-Ymacro-debug-lite")

    Warning:scalac: 42.pure[List]
    TypeApply(Select(Literal(Constant(42)), TermName("pure")), List(TypeTree()))
    

    at compile time and List(42) at runtime.

    lift[({ type F[A] = Either[String, A] })#F](42) produces

    Warning:scalac: 42.pure[[A]scala.util.Either[String,A]]
    TypeApply(Select(Literal(Constant(42)), TermName("pure")), List(TypeTree()))
    

    at compile time and Right(42) at runtime.

    This is my project https://gist.github.com/DmytroMitin/334c230a4f2f1fd3fe9e7e5a3bb10df5


    Why do you need macros? Why can't you write

    import cats.Applicative 
    import cats.syntax.applicative._ 
    
    class LiftPartiallyApplied[F[_]: Applicative] { 
      def apply[A](a: A): F[A] = a.pure[F] 
    } 
    
    def lift[F[_]: Applicative] = new LiftPartiallyApplied[F] 
    

    ?