Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 991
Matthias Berndt Avatar asked Mar 04 '23 22:03

Matthias Berndt


2 Answers

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] 

?

like image 199
Dmytro Mitin Avatar answered Mar 07 '23 04:03

Dmytro Mitin


Allright, I found out what the problem was.

Macros need to be compiled separately from their use sites. I thought that this meant that Macros needs to be compiled separately from MacroImpl, so I put those in separate sbt subprojects, and I called the macro in the project where Macros is defined. But what it in fact means is that calls of the macro need to be compiled separately from its definition. So I put MacroImpl and Macros in one subproject and called the macro in another and it worked perfectly.

Thanks to Dmytro for taking the time to demonstrate how to do it right!

// edit: looks like Dmytro beat me to it with his comment :-)

like image 34
Matthias Berndt Avatar answered Mar 07 '23 02:03

Matthias Berndt