如何在 Scala 宏中获取更高类型参数的树
How to obtain a tree for a higher-kinded type parameter in a scala macro
我正在尝试编写一个宏来简化一些与 monad 相关的代码(我正在为 Monad 使用 cats 1.6.0)。现在我只想能够编写 lift[F](a)
,其中 F
是一元类型构造函数,并将其扩展为 a.pure[F]
。看起来很简单,但我无法让它工作。
现在我有这段代码可以帮助进行类型推断:
object Macros {
class LiftPartiallyApplied[F[_]] {
def apply[A](a: A): F[A] = macro MacroImpl.liftImpl[F, A]
}
def lift[F[_]] = new LiftPartiallyApplied[F]
}
而对于宏的实际执行:
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}]"
}
}
现在我可以像这样调用宏 lift[List](42)
,它会扩展为 42.pure[List]
,太棒了。但是当我用更复杂的类型调用它时,比如 lift[({type F[A] = Either[String, A]})#F](42)
,它会扩展为 42.pure[Either]
,这显然是错误的,因为 Either
是二进制类型构造函数而不是一元类型构造函数.问题是我只是不知道该放什么而不是 ${tt.tpe.typeConstructor}
…
// 编辑:由于人们显然无法重现问题,我制作了一个完整的存储库:
https://github.com/mberndt123/macro-experiment
我现在将尝试找出 Dmytro 的项目和我自己的项目之间的区别。
不要把Main
和Macros
放到同一个编译单元。
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]
无法重现。
对我来说 lift[List](42)
产生(scalacOptions += "-Ymacro-debug-lite"
)
Warning:scalac: 42.pure[List]
TypeApply(Select(Literal(Constant(42)), TermName("pure")), List(TypeTree()))
在编译时,List(42)
在运行时。
lift[({ type F[A] = Either[String, A] })#F](42)
产生
Warning:scalac: 42.pure[[A]scala.util.Either[String,A]]
TypeApply(Select(Literal(Constant(42)), TermName("pure")), List(TypeTree()))
在编译时,Right(42)
在运行时。
这是我的项目https://gist.github.com/DmytroMitin/334c230a4f2f1fd3fe9e7e5a3bb10df5
为什么需要宏?为什么不能写
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]
?
好的,我找到问题所在了。
宏需要与其使用站点分开编译。我认为这意味着 Macros
需要与 MacroImpl
分开编译,所以我将它们放在单独的 sbt 子项目中,并在定义 Macros
的项目中调用宏。但它实际上意味着宏的 calls 需要与其定义分开编译。所以我把 MacroImpl
和 Macros
放在一个子项目中,并在另一个子项目中调用宏,它运行得很好。
感谢 Dmytro 抽出时间来演示如何正确操作!
// 编辑:看起来 Dmytro 用他的评论打败了我:-)
我正在尝试编写一个宏来简化一些与 monad 相关的代码(我正在为 Monad 使用 cats 1.6.0)。现在我只想能够编写 lift[F](a)
,其中 F
是一元类型构造函数,并将其扩展为 a.pure[F]
。看起来很简单,但我无法让它工作。
现在我有这段代码可以帮助进行类型推断:
object Macros {
class LiftPartiallyApplied[F[_]] {
def apply[A](a: A): F[A] = macro MacroImpl.liftImpl[F, A]
}
def lift[F[_]] = new LiftPartiallyApplied[F]
}
而对于宏的实际执行:
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}]"
}
}
现在我可以像这样调用宏 lift[List](42)
,它会扩展为 42.pure[List]
,太棒了。但是当我用更复杂的类型调用它时,比如 lift[({type F[A] = Either[String, A]})#F](42)
,它会扩展为 42.pure[Either]
,这显然是错误的,因为 Either
是二进制类型构造函数而不是一元类型构造函数.问题是我只是不知道该放什么而不是 ${tt.tpe.typeConstructor}
…
// 编辑:由于人们显然无法重现问题,我制作了一个完整的存储库: https://github.com/mberndt123/macro-experiment 我现在将尝试找出 Dmytro 的项目和我自己的项目之间的区别。
不要把Main
和Macros
放到同一个编译单元。
But when I call it with a more complicated type, like
lift[({type F[A] = Either[String, A]})#F](42)
, It'll expand to42.pure[Either]
无法重现。
对我来说 lift[List](42)
产生(scalacOptions += "-Ymacro-debug-lite"
)
Warning:scalac: 42.pure[List]
TypeApply(Select(Literal(Constant(42)), TermName("pure")), List(TypeTree()))
在编译时,List(42)
在运行时。
lift[({ type F[A] = Either[String, A] })#F](42)
产生
Warning:scalac: 42.pure[[A]scala.util.Either[String,A]]
TypeApply(Select(Literal(Constant(42)), TermName("pure")), List(TypeTree()))
在编译时,Right(42)
在运行时。
这是我的项目https://gist.github.com/DmytroMitin/334c230a4f2f1fd3fe9e7e5a3bb10df5
为什么需要宏?为什么不能写
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]
?
好的,我找到问题所在了。
宏需要与其使用站点分开编译。我认为这意味着 Macros
需要与 MacroImpl
分开编译,所以我将它们放在单独的 sbt 子项目中,并在定义 Macros
的项目中调用宏。但它实际上意味着宏的 calls 需要与其定义分开编译。所以我把 MacroImpl
和 Macros
放在一个子项目中,并在另一个子项目中调用宏,它运行得很好。
感谢 Dmytro 抽出时间来演示如何正确操作!
// 编辑:看起来 Dmytro 用他的评论打败了我:-)