2014-09-23 77 views
0

我遇到了一些问题,我写了一个宏帮助我将作为案例类实例表示的度量记录到InfluxDB。我认为我有一个类型擦除问题,并且tyep参数T正在丢失,但我不完全确定发生了什么。 (这也是我第一次接触到斯卡拉宏)Scala宏和类型擦除

import scala.language.experimental.macros 
import play.api.libs.json.{JsNumber, JsString, JsObject, JsArray} 

abstract class Metric[T] { 
    def series: String 

    def jsFields: JsArray = macro MetricsMacros.jsFields[T] 
    def jsValues: JsArray = macro MetricsMacros.jsValues[T] 
} 

object Metrics { 
    case class LoggedMetric(timestamp: Long, series: String, fields: JsArray, values: JsArray) 
    case object Kick 

    def log[T](metric: Metric[T]): Unit = { 
     println(LoggedMetric(
      System.currentTimeMillis, 
      metric.series, 
      metric.jsFields, 
      metric.jsValues 
     )) 
    } 
} 

而且这里有一个例子度量情况类:

case class SessionCountMetric(a: Int, b: String) extends Metric[SessionCountMetric] { 
    val series = "sessioncount" 
} 

这里发生了什么,当我尝试登录它:

scala> val m = SessionCountMetric(1, "a") 
m: com.confabulous.deva.SessionCountMetric = SessionCountMetric(1,a) 

scala> Metrics.log(m) 
LoggedMetric(1411450638296,sessioncount,[],[]) 

即使宏本身似乎工作正常:

scala> m.jsFields 
res1: play.api.libs.json.JsArray = ["a","b"] 

scala> m.jsValues 
res2: play.api.libs.json.JsArray = [1,"a"] 

这里的实际宏本身:

import scala.language.experimental.macros 
import scala.reflect.macros.blackbox.Context 

object MetricsMacros { 
    private def fieldNames[T: c.WeakTypeTag](c: Context)= { 
     val tpe = c.weakTypeOf[T] 
     tpe.decls.collect { 
      case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name 
     } 
    } 

    def jsFields[T: c.WeakTypeTag](c: Context) = { 
     import c.universe._ 
     val names = fieldNames[T](c) 
     Apply(
      q"play.api.libs.json.Json.arr", 
      names.map(name => Literal(Constant(name.toString))).toList 
     ) 
    } 

    def jsValues[T: c.WeakTypeTag](c: Context) = { 
     import c.universe._ 
     val names = fieldNames[T](c) 
     Apply(
      q"play.api.libs.json.Json.arr", 
      names.map(name => q"${c.prefix.tree}.$name").toList 
     ) 
    } 
} 

更新

我想尤金的第二个建议是这样的:

abstract class Metric[T] { 
    def series: String 
} 

trait MetricSerializer[T] { 
    def fields: Seq[String] 
    def values(metric: T): Seq[Any] 
} 

object MetricSerializer { 
    implicit def materializeSerializer[T]: MetricSerializer[T] = macro MetricsMacros.materializeSerializer[T] 
} 

object Metrics { 
    def log[T: MetricSerializer](metric: T): Unit = { 
     val serializer = implicitly[MetricSerializer[T]] 
     println(serializer.fields) 
     println(serializer.values(metric)) 
    } 
} 

与宏现在看起来像这样:

object MetricsMacros { 
    def materializeSerializer[T: c.WeakTypeTag](c: Context) = { 
     import c.universe._ 

     val tpe = c.weakTypeOf[T] 
     val names = tpe.decls.collect { 
      case field if field.isMethod && field.asMethod.isCaseAccessor => field.asTerm.name 
     } 

     val fields = Apply(
      q"Seq", 
      names.map(name => Literal(Constant(name.toString))).toList 
     ) 

     val values = Apply(
      q"Seq", 
      names.map(name => q"metric.$name").toList 
     ) 

     q""" 
      new MetricSerializer[$tpe] { 
       def fields = $fields 
       def values(metric: Metric[$tpe]) = $values 
      } 
     """ 
    } 
} 

但是,当我拨打Metrics.log - 具体是什么时候它要求implicitly[MetricSerializer[T]]我得到以下错误:

error: value a is not a member of com.confabulous.deva.Metric[com.confabulous.deva.SessionCountMetric] 

为什么要使用Metric[com.confabulous.deva.SessionCountMetric]代替SessionCountMetric

结论

修正了它。

def values(metric: Metric[$tpe]) = $values 

应该已经

def values(metric: $tpe) = $values 

回答

2

你在的情况下这是非常接近在最近的问题描述:scala macros: defer type inference

由于现在的情况,您必须将log变成宏。另一种方法是将Metric.jsFieldsMetric.jsValues转换为JsFieldableJsValuable类型类,该类型由隐含宏在loghttp://docs.scala-lang.org/overviews/macros/implicits.html)的调用值处实现。

+0

我试过了你的选择,因为它看上去总体上比较干净整洁,但是我碰到了另一个问题。请参阅我编辑中的更新。 – Derecho 2014-09-24 05:33:58

+0

其实,这是一个快速解决方案 - 请参阅结论。谢谢! – Derecho 2014-09-24 05:47:01

+0

它很有效! – 2014-09-24 09:17:25