【问题标题】:Scala: Boilerplate-free pimpingScala:无样板的拉皮条
【发布时间】:2012-10-24 17:49:18
【问题描述】:

我广泛使用Pimp my Library 模式,我想删除样板。例如,假设我有一些特质 PrettyPrint:

trait PrettyPrint { def prettyPrint: String }

如果我想拉皮条 Int 和 Double,我需要编写如下代码:

implicit def int2PrettyPrint(self: Int) = 
  new PrettyPrint { def prettyPrint = "Int: " + self }
implicit def double2PrettyPrint(self: Double) = 
  new PrettyPrint { def prettyPrint = "Double: " + self }

在上面,我将其归类为样板:1)隐式转换的名称,2)“new”关键字,3)也许是参数名称“self”,4)也许是“隐式”关键字。我宁愿写这样的东西:

@pimp[Int, PrettyPrint] { def prettyPrint = "Int: " + self }
@pimp[Double, PrettyPrint] { def prettyPrint = "Double: " + self }

在上述代码的右侧,名称“self”被假定为转换参数。

关于如何做到这一点的想法?

一些注意事项:

1) 如有必要,我愿意使用 Scala 2.10。

2) 据我所知,Scala 2.10 中的新隐式类还不够。这是因为每个隐式类只有一个隐式转换。换句话说,像下面这样的代码不会编译,因为 PrettyPrint 被声明了两次:

implicit class PrettyPrint(self: Int) = ...
implicit class PrettyPrint(self: Double) = ...

【问题讨论】:

  • @pr1001,是的。你有如何做到这一点的指针,或者有好的阅读链接吗?
  • 到目前为止,宏无法进行全局可见的更改(例如添加公共成员或类)。我们可能会在 2.10.x 版本之一中添加此功能,但没有承诺。
  • @EugeneBurmako,那给我留下了什么?一个注解加一个编译器插件?
  • 不是来自我,因为我并没有真正做 SBT/Eclipse 的事情。但鉴于我在 20 年前使用 C 代码生成器自动构建东西,我不得不想象它仍然是可能的。
  • @emchristiansen 看起来这是满足您所有要求的唯一方法。

标签: scala metaprogramming implicit-conversion scala-2.10 scala-macros


【解决方案1】:

你可以用不同的方式命名你的隐式类:

implicit class PrettyPrintInt(self: Int) = ...
implicit class PrettyPrintDouble(self: Double) = ...

【讨论】:

  • 这有两个问题:1)它只是稍微减少了样板,2)(主要问题)它改变了语义。如果我有一些函数“def foo(prettyPrint: PrettyPrint) ...”,我不能再向它传递一个 Int 或一个 Double。
  • @emchristiansen:为什么不呢? PrettyPrint 是一个特征,隐式类可以扩展 PrettyPrint
  • @sschaef,如果你这样做,你最终不会保存字符。隐式类的全部意义在于将特征定义和隐式定义合并为一个定义。如果您使用隐式类但还必须定义 PrettyPrint 超级特征,您将失去节省的成本。另外,您必须在每个隐式类定义中编写“扩展 PrettyPrint”。
【解决方案2】:

这是另一种解决方案,它需要显着更多的样板代码,以换取PrettyPrint 的每个特定实例的混乱程度略低:

implicit class PrettyPrintable[T]( val self: T ) extends AnyVal { 
  def prettyPrint( implicit impl: PrettyPrint[T]): String = impl.prettyPrint( self ) 
}
trait PrettyPrint[T]{ def prettyPrint( self: T ): String }
object PrettyPrint {
  def apply[T]( impl: T => String ): PrettyPrint[T] = new PrettyPrint[T] {
    def prettyPrint( self: T ) = impl( self )
  }
}

implicit val int2PrettyPrint = PrettyPrint[Int]( "Int: " + _ )
implicit val double2PrettyPrint = PrettyPrint[Double]( "Double: " + _ )
// Or more explicitly:
//implicit val int2PrettyPrint = PrettyPrint{self: Int => "Int: " + self }
//implicit val double2PrettyPrint = PrettyPrint{self: Double => "Double: " + self }

比较:

implicit def int2PrettyPrint(self: Int) = new PrettyPrint { def prettyPrint = "Int: " + self } 

到:

implicit val int2PrettyPrint = PrettyPrint[Int]( "Int: " + _ )

您仍然需要 implicit 关键字,以及隐式值的唯一名称

【讨论】:

  • +1 这很好,但在我看来编译器插件是可行的方法。
  • 顺便说一句,您的答案中的“扩展 AnyVal”不是不必要的吗?我认为所有引用类型都会自动扩展 AnyVal。
  • 不,它们默认扩展 AnyRef。扩展 AnyVal 使其成为“值类”(仅在 scala 2.10 中有效(。您可以删除它,它只是一种优化。
  • 请注意,虽然我使用了 scala 2.10 的另一个特性:PrettyPrintable 是一个隐式类。对于scala
【解决方案3】:

1 周后总结:看来我需要编写一个编译器插件来获得我指定的确切行为。

【讨论】:

    【解决方案4】:

    在 NativeLibs4Java 的邮件列表中跟进我们的 discussion,我在其中举了一个 such a compiler plugin 的示例(将 @extend(Int) def foo = blah 扩展为 implicit class foo(self: Int) extends AnyVal { def foo = blah })。

    我编写了a more elaborated plugin,将这些定义扩展为...宏(提供宏可扩展扩展/“pimps”,不依赖运行时!)。

    给定:

    @extend(Any) def quoted(quote: String): String = quote + self + quote
    

    它扩展为:

    import scala.language.experimental.macros
    implicit class scalaxy$extensions$quoted$1(self: Any) {
      def quoted(quote: String) = macro scalaxy$extensions$quoted$1.quoted
    }
    object scalaxy$extensions$quoted$1 {
      def quoted(c: scala.reflect.macros.Context)
                (quote: c.Expr[String]): c.Expr[String] = {
        import c.universe._
        val Apply(_, List(selfTree$1)) = c.prefix.tree
        val self = c.Expr[Any](selfTree$1)
        {
          reify(quote.splice + self.splice + quote.splice)
        }
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-06-24
      • 2013-04-24
      • 1970-01-01
      • 2021-03-05
      • 2018-10-24
      • 2011-05-27
      • 1970-01-01
      • 2015-08-03
      • 2017-06-20
      相关资源
      最近更新 更多