Scala: как заставить преобразовать оператор в буквальный?

Я экспериментирую с усовершенствованной функцией типа 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 типа там, кажется, нет возможности предложить это.

Так чем же может быть полезна эта функция?

2 ответа

Решение

Дело в том, что eu.timepit.refined макросы работают с литералами, BigDecimal, BigInt

https://github.com/fthomas/refined/blob/master/modules/core/shared/src/main/scala/eu/timepit/refined/macros/RefineMacro.scala

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.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0

Судя по тестам на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 может быть непростым и подверженным ошибкам. Конечно, можно было бы добавить несколько "очевидных особых случаев", но лично я предпочитаю, чтобы автор библиотеки вместо этого сосредоточился на чем-то более полезном.

Другие вопросы по тегам