2014-09-09 7 views
4

Преамбула: это основано на @Travis Brown's macro based solution для копирования свойств класса case.scala macros: defer тип вывода

Дано:

trait Entity[E <: Entity[E]]{self:E=> 
    def id: Int 
    def withId(id: Int) = MacroCopy.withId(self,id) 
} 
case class User(id: Int, name: String) extends Entity[User] 

и Macro реализация:

object MacroCopy { 
    import scala.language.experimental.macros 
    import scala.reflect.macros.blackbox.Context 
    def withId[T](entity: T, id: Int): T = macro withIdImpl[T] 

    def withIdImpl[T: c.WeakTypeTag] 
    (c: Context)(entity: c.Expr[T], id: c.Expr[Int]): c.Expr[T] = { 
    import c.universe._ 

    val tree = reify(entity.splice).tree 
    val copy = entity.actualType.member(TermName("copy")) 

    val params = copy match { 
     case s: MethodSymbol if (s.paramLists.nonEmpty) => s.paramLists.head 
     case _ => c.abort(c.enclosingPosition, "No eligible copy method!") 
    } 
    c.Expr[T](Apply(
     Select(tree, copy), 
     AssignOrNamedArg(Ident(TermName("id")), reify(id.splice).tree) :: Nil 
    )) 
    } 
} 

есть способ, чтобы каким-то образом перенести определение типа таким образом, что макрос работает на себя субъекта User и не тип? Поскольку это означает, что средство проверки типов ничего не знает о методе копирования класса case , поскольку все, что он видит, является значением типа E.

Я хотел бы сделать:

val u = User(2,"foo") 
u.withId(3) 

Большинство из альтернативных решений, которые я видел влекут за собой определение withId, как абстрактные в черте Entity, а затем реализует метод в каждый случай класса, предпочли бы чтобы избежать этого, если это возможно.

+1

у меня нет времени, чтобы смотреть на это подробно на данный момент, но вы видели [это мое сообщение в блоге] (http://meta.plasm.us/posts/2013/06/21/macro-methods-and-subtypes/)? –

+0

@TravisBrown благодарит, что сделал мне еще один шаг, теперь можно делать 'u.withId (u, 3)'. Похоже, что контекст связан с тем, чтобы избежать необходимости проходить в том экземпляре, который у вас уже есть. Дайте ему повод в AM ... – virtualeyes

+0

Кстати, вопрос о вашем посте @TravisBrown. Почему бы не использовать c.prefix.tree.tpe? –

ответ

0

weakTypeOf В сочетании с контекстом, связанным с классом экземпляра, предоставляется желаемый вывод типа «с задержкой».

trait Entity[E <: Entity[E]]{self:E=> 
    def id: Int 
    def withId(id: Int) = MacroCopy.withIdImpl[E] 
} 
case class User(id: Int, name: String) extends Entity[User] 

object MacroCopy { 
    import scala.language.experimental.macros 
    import scala.reflect.macros.blackbox.Context 

    def withIdImpl[T <: Entity[T]: c.WeakTypeTag] // context bound on Entity 
    (c: Context)(id: c.Expr[Int]): c.Expr[T] = { 
    import c.universe._ 

    val tree = reify(c.Expr[T](c.prefix.tree).splice).tree 
    val copy = weakTypeOf[T].member(TermName("copy")) // now lookup case class' copy method 
    val params = copy match { 
     case s: MethodSymbol if (s.paramLists.nonEmpty) => s.paramLists.head 
     case _ => c.abort(c.enclosingPosition, "No eligible copy method!") 
    } 
    c.Expr[T](Apply(
     Select(tree, copy), 
     AssignOrNamedArg(Ident(TermName("id")), reify(id.splice).tree) :: Nil 
    )) 
    } 
} 

Теперь мы можем написать foo.withId(2) вместо предыдущей попытки, foo.withId(foo, 2), удивительно краткой. Может быть интересно, почему бы просто не сделать: foo.copy(id = 2)? Для конкретного случая, который работает отлично, но когда вам нужно применить это на более абстрактном уровне, то это работает совсем не.

Не работает и следующее: кажется, мы должны работать с конкретными примерами классов case, поэтому close ;-( Например, у вас есть DAO, и вы хотите, чтобы все обновленные сущности имели действительный идентификатор. выше макрос позволяет сделать что-то вроде:.

def update[T <: Entity[T]](entity: T, id: Int)(implicit ss: Session): Either[String,Unit] = { 
    either(byID(id).mutate(_.row = entity.withId(id)), i18n("not updated")) 
} 

Поскольку Entity является чертой, а не класс так, без макроса не будет никакого времени компиляции способ имитации entity.copy(id = id) в качестве обходного пути я имею переопределенный метод обновления DAO следующим образом:

def update[T <: Entity[T]](fn: id => T, id: Int)(implicit ss: Session): Either[String,Unit] = { 
    either(byID(id).mutate(_.row = fn(id)), i18n("not updated")) 
} 

Это, по крайней мере, заставляет один из них использовать функцию u.withId (_: Int) для метода обновления. Лучше, чем иметь потенциально недействительные объекты во время выполнения, но все же не так изящны, как выполнение с Ид прямо перед тем, как это имеет значение (т.е. сохраняется в БД), тем самым избегая работы мула (шаблона) передачи конкретного экземпляра функции для обновления, вздох, способ сделать это с помощью макросов.

В других новостях, написав свой первый макрос, я действительно нравится потенциал здесь, удивительный материал ;-)