Scala:如何强制将语句转换为文字?
Scala: how to force converting a statement to literal?
我正在试验它的一个库中提供的 scala 的改进类型功能:
https://github.com/fthomas/refined
以下代码代表一个简单的案例:
import eu.timepit.refined.auto._
import shapeless.{Witness => W}
type Vec5 = List[Int] Refined Size[Equal[W.`5`.T]]
val v1: Vec5 = List(1, 2, 3, 4, 5)
val v2: Vec5 = List(1 to 5: _*)
尝试编译时出现以下错误:
[Error] /home/peng/git/scalaspike/common/src/test/scala/com/tribbloids/spike/refined_spike/Example.scala:32: compile-time refinement only works with literals
[Error] /home/peng/git/scalaspike/common/src/test/scala/com/tribbloids/spike/refined_spike/Example.scala:34: compile-time refinement only works with literals
[Error] /home/peng/git/scalaspike/common/src/test/scala/com/tribbloids/spike/singleton_ops_spike/Example.scala:32: Cannot prove requirement Require[...]
three errors found
需要注意的是,v1 和 v2 都可以在编译时轻松评估并内联,但是 scala 编译器似乎拒绝这样做,并且对于 List
类型似乎没有办法建议这个。
那么这个功能有什么用呢?
根据 tests 判断,Size[Equals[X]]
编译时提升仅在 String
文字的宏中实现。
顺便说一句,这是有道理的,因为作者必须在编译时评估代码——List(1,2,3,4,5)
可能看起来很简单,但 Set(1,1,2,2,3,3)
需要一些评估,如果代码评估是 List(1,1,2,2,3,3).distinct
- 它也可以在编译时解决,但你必须在某处设置行,除非你想冒险执行任意代码。即使在更简单的情况下,要分析的 ADT 也可能很复杂且容易出错。当然,可以添加一些 "obvious special cases",但就我个人而言,我更希望库的作者专注于更有用的东西。
问题是 eu.timepit.refined
宏适用于文字,BigDecimal
,BigInt
def impl[F[_, _], T: c.WeakTypeTag, P: c.WeakTypeTag](t: c.Expr[T])(
rt: c.Expr[RefType[F]],
v: c.Expr[Validate[T, P]]
): c.Expr[F[T, P]] = {
val tValue: T = t.tree match {
case Literal(Constant(value)) => value.asInstanceOf[T]
case BigDecimalMatcher(value) => value.asInstanceOf[T]
case BigIntMatcher(value) => value.asInstanceOf[T]
case _ => abort(Resources.refineNonCompileTimeConstant)
}
List(1, 2, 3, 4, 5)
不是文字。
对于像 List(1, 2, 3, 4, 5)
这样的非文字值,有 refineV
在 运行 时间
工作
val v1 = List(1, 2, 3, 4, 5)
val v2 = List(1, 2, 3, 4, 5, 6)
refineV[Size[Equal[5]]](v1)
// Right(List(1, 2, 3, 4, 5))
refineV[Size[Equal[5]]](v2)
// Left(Predicate taking size(List(1, 2, 3, 4, 5, 6)) = 6 failed: Predicate failed: (6 == 5).)
幸运的是你可以运行refineV
在编译时
object myAuto {
implicit def compileTimeRefineV[T, P](t: T): T Refined P =
macro compileTimeRefineVImpl[T, P]
def compileTimeRefineVImpl[T: c.WeakTypeTag,
P: c.WeakTypeTag](c: blackbox.Context)(t: c.Tree): c.Tree = {
import c.universe._
val pTyp = weakTypeOf[P]
val tTyp = weakTypeOf[T]
c.eval(c.Expr[Either[String, T Refined P]](c.untypecheck(
q"_root_.eu.timepit.refined.`package`.refineV[$pTyp].apply[$tTyp]($t)"
))).fold(
c.abort(c.enclosingPosition, _),
_ => q"$t.asInstanceOf[_root_.eu.timepit.refined.api.Refined[$tTyp, $pTyp]]"
)
}
}
import myAuto._ // don't import eu.timepit.refined.auto._
type Vec5 = List[Int] Refined Size[Equal[5]]
val v1: Vec5 = List(1, 2, 3, 4, 5) // compiles
// val v2: Vec5 = List(1, 2, 3, 4, 5, 6)
// Error: Predicate taking size(List(1, 2, 3, 4, 5, 6)) = 6 failed: Predicate failed: (6 == 5).
如果您只需要静态大小的集合,您可以使用 shapeless.Sized
我正在试验它的一个库中提供的 scala 的改进类型功能:
https://github.com/fthomas/refined
以下代码代表一个简单的案例:
import eu.timepit.refined.auto._
import shapeless.{Witness => W}
type Vec5 = List[Int] Refined Size[Equal[W.`5`.T]]
val v1: Vec5 = List(1, 2, 3, 4, 5)
val v2: Vec5 = List(1 to 5: _*)
尝试编译时出现以下错误:
[Error] /home/peng/git/scalaspike/common/src/test/scala/com/tribbloids/spike/refined_spike/Example.scala:32: compile-time refinement only works with literals
[Error] /home/peng/git/scalaspike/common/src/test/scala/com/tribbloids/spike/refined_spike/Example.scala:34: compile-time refinement only works with literals
[Error] /home/peng/git/scalaspike/common/src/test/scala/com/tribbloids/spike/singleton_ops_spike/Example.scala:32: Cannot prove requirement Require[...]
three errors found
需要注意的是,v1 和 v2 都可以在编译时轻松评估并内联,但是 scala 编译器似乎拒绝这样做,并且对于 List
类型似乎没有办法建议这个。
那么这个功能有什么用呢?
根据 tests 判断,Size[Equals[X]]
编译时提升仅在 String
文字的宏中实现。
顺便说一句,这是有道理的,因为作者必须在编译时评估代码——List(1,2,3,4,5)
可能看起来很简单,但 Set(1,1,2,2,3,3)
需要一些评估,如果代码评估是 List(1,1,2,2,3,3).distinct
- 它也可以在编译时解决,但你必须在某处设置行,除非你想冒险执行任意代码。即使在更简单的情况下,要分析的 ADT 也可能很复杂且容易出错。当然,可以添加一些 "obvious special cases",但就我个人而言,我更希望库的作者专注于更有用的东西。
问题是 eu.timepit.refined
宏适用于文字,BigDecimal
,BigInt
def impl[F[_, _], T: c.WeakTypeTag, P: c.WeakTypeTag](t: c.Expr[T])(
rt: c.Expr[RefType[F]],
v: c.Expr[Validate[T, P]]
): c.Expr[F[T, P]] = {
val tValue: T = t.tree match {
case Literal(Constant(value)) => value.asInstanceOf[T]
case BigDecimalMatcher(value) => value.asInstanceOf[T]
case BigIntMatcher(value) => value.asInstanceOf[T]
case _ => abort(Resources.refineNonCompileTimeConstant)
}
List(1, 2, 3, 4, 5)
不是文字。
对于像 List(1, 2, 3, 4, 5)
这样的非文字值,有 refineV
在 运行 时间
val v1 = List(1, 2, 3, 4, 5)
val v2 = List(1, 2, 3, 4, 5, 6)
refineV[Size[Equal[5]]](v1)
// Right(List(1, 2, 3, 4, 5))
refineV[Size[Equal[5]]](v2)
// Left(Predicate taking size(List(1, 2, 3, 4, 5, 6)) = 6 failed: Predicate failed: (6 == 5).)
幸运的是你可以运行refineV
在编译时
object myAuto {
implicit def compileTimeRefineV[T, P](t: T): T Refined P =
macro compileTimeRefineVImpl[T, P]
def compileTimeRefineVImpl[T: c.WeakTypeTag,
P: c.WeakTypeTag](c: blackbox.Context)(t: c.Tree): c.Tree = {
import c.universe._
val pTyp = weakTypeOf[P]
val tTyp = weakTypeOf[T]
c.eval(c.Expr[Either[String, T Refined P]](c.untypecheck(
q"_root_.eu.timepit.refined.`package`.refineV[$pTyp].apply[$tTyp]($t)"
))).fold(
c.abort(c.enclosingPosition, _),
_ => q"$t.asInstanceOf[_root_.eu.timepit.refined.api.Refined[$tTyp, $pTyp]]"
)
}
}
import myAuto._ // don't import eu.timepit.refined.auto._
type Vec5 = List[Int] Refined Size[Equal[5]]
val v1: Vec5 = List(1, 2, 3, 4, 5) // compiles
// val v2: Vec5 = List(1, 2, 3, 4, 5, 6)
// Error: Predicate taking size(List(1, 2, 3, 4, 5, 6)) = 6 failed: Predicate failed: (6 == 5).
如果您只需要静态大小的集合,您可以使用 shapeless.Sized