dotty-feature-requests icon indicating copy to clipboard operation
dotty-feature-requests copied to clipboard

Reducing frictions when use quoted expressions with tasty reflect

Open liufengyun opened this issue 6 years ago • 6 comments

The problem can be demonstrated below, which is encountered in https://github.com/scalatest/scalatest/pull/1584:

def default: Term = {
    type T
    implicit val tp: quoted.Type[T] = expr.tpe.seal.asInstanceOf[quoted.Type[T]]
    '{ DiagrammedExpr.simpleExpr(${expr.seal.cast[T]}, ${ getAnchor(expr) } ) }.unseal
}

// another use case

val left = l.seal.cast[DiagrammedExpr[T]]
val right = r.seal.cast[DiagrammedExpr[_]]
val res = apply('{$left.value}.unseal, op, Nil, '{$right.value}.unseal :: Nil).seal.cast[R]
'{ DiagrammedExpr.applyExpr[R]($left, $right :: Nil, $res, $anchor) }.unseal

There are several usability issues here:

  • P1: boilerplate to declare T and the implicittp

  • P2: the call .seal.cast could be simplified to be just one method call

  • P3: the call .unseal is annoying, given the expected type is Term

  • [x] P4: writing ${x.toExpr} is annoying when x is primitive type import scala.quoted.autolift

  • [x] P5: tree.show(the[Context].withoutColors) is annoying, should default no color

liufengyun avatar May 28 '19 15:05 liufengyun

The first use case could be improved if inside Term there was a type T abstract member along with an implicit def T: Type[T], to be used as:

    def default: Term = {
      import expr.T
      '{ DiagrammedExpr.simpleExpr(${expr.seal}, ${ getAnchor(expr) } ) }.unseal
    }

This way, you also retain more type safety (no unsafe casts in this case). One can also rename the import if several such types are imported, as in import expr.{T => exprT}.

That's the kind of tricks I used in Squid.

LPTK avatar May 28 '19 15:05 LPTK

@LPTK Sounds like a very clever idea to me. WDYT @nicolasstucki?

liufengyun avatar May 28 '19 15:05 liufengyun

That is exactly where I planned to place the abstract type T but I was missing the implicit def T trick to import them. I will try it out. Thanks @LPTK.

nicolasstucki avatar May 28 '19 15:05 nicolasstucki

Right, Term is defined as type Term <: Statement. I can add the type member but not the implicit def

nicolasstucki avatar May 28 '19 16:05 nicolasstucki

I'm not sure if we can do something as follows:

// library
trait TypeEvidence {
  type T
  val tpe: Type[T]
}

implicit def getType(implicit ev: TypeEvidence): ev.Type[ev.T] = ev.tpe

// user code
def default: Term = {
    implicit val ev = expr.typeEvidence
    '{ DiagrammedExpr.simpleExpr(${expr.seal.cast[ev.T]}, ${ getAnchor(expr) } ) }.unseal
}

liufengyun avatar May 28 '19 16:05 liufengyun

Another idea to reduce .seal.cast:

// library
trait TypeEvidence[O] {
  type T
  val tpe: Type[T]
}

implicit def getType(implicit ev: TypeEvidence): ev.Type[ev.T] = ev.tpe

implicit def toExpr(expr: Term)(implicit ev: TypeEvidence[expr.type]): Expr[ev.T]
    = expr.seal.cast[ev.T]

// user code
def default: Term = {
    implicit val ev: TypeEvidence[expr] = expr.typeEvidence
    '{ DiagrammedExpr.simpleExpr($expr, ${ getAnchor(expr) } ) }
}

liufengyun avatar May 28 '19 16:05 liufengyun