Scala:如何强制将语句转换为文字?

问题描述 投票:2回答:1

我正在试验Scala的一个库中提供的精炼类型功能。

https:/github.comfthomasrefined

下面的代码代表了一个简单的案例。

  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 型似乎没有办法提出这个建议。

那么这个功能如何才能有用呢?

scala dependent-type refined
1个回答
4
投票

事情是这样的 eu.timepit.refined 宏适用于文字。BigDecimal, BigInt

https:/github.comfthomasrefinedblobmastermodulescoresharedsrcmainscalaeutimepitrefinedmacrosRefineMacro.scala#L14-L23。

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

https:/github.commilessabinshapelesswikiFeature-overview:-shapeless-2.0.0#collections-with-statically-known-sizes。


5
投票

判断标准 测试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也可能会很毛躁,容易出错。当然,可以增加一些 "明显的特殊情况",但我个人更希望库的作者专注于更有用的东西,而不是。

© www.soinside.com 2019 - 2024. All rights reserved.