【问题标题】:Elegant grouping of implicit value classes隐式值类的优雅分组
【发布时间】:2018-07-23 06:17:28
【问题描述】:

我正在为现有的 Java 库编写一组隐式 Scala 包装类(以便我可以装饰该库以制作Scala 开发人员更方便)。

作为一个简单的例子,假设 Java 库(我无法修改)具有如下类:

public class Value<T> {
    // Etc.
    public void setValue(T newValue) {...}
    public T getValue() {...}
}

现在假设我想用 Scala 风格的 getter 和 setter 来装饰这个类。我可以使用以下隐式类来做到这一点:

final implicit class RichValue[T](private val v: Value[T])
extends AnyVal {
  // Etc.
  def value: T = v.getValue
  def value_=(newValue: T): Unit = v.setValue(newValue)
}

implicit 关键字告诉 Scala 编译器它可以将Value 的实例隐式转换为RichValue 的实例(前提是后者在范围内)。所以现在我可以将RichValue 中定义的方法应用于Value 的实例。例如:

def increment(v: Value[Int]): Unit = {
  v.value = v.value + 1
}

(同意,这不是很好的代码,也不完全是功能。我只是想演示一个简单的用例。)

不幸的是,Scala 不允许 implicit 类是顶级的,因此它们必须在 package objectobjectclasstrait 中定义,而不是就在package。 (我不知道为什么这个限制是必要的,但我认为它是为了与隐式转换函数兼容。)

不过,我还从AnyVal 扩展了RichValue 以使其成为值类。如果您不熟悉它们,它们允许 Scala 编译器进行分配优化。具体来说,编译器并不总是需要创建RichValue的实例,可以直接对值类的构造函数参数进行操作。

换句话说,使用 Scala 隐式值类 作为包装器几乎没有性能开销,这很好。 :-)

然而,值类的一个主要限制是它们不能在classtrait 中定义;他们只能是packages、package objects 或objects 的成员。 (这样他们就不需要维护指向外部类实例的指针了。)

隐式值类必须遵守两组约束,因此它只能在package objectobject 中定义。

这就是问题所在。我要包装的库包含具有大量类和接口的深层包层次结构。理想情况下,我希望能够使用单个 import 语句导入我的包装类,例如:

import mylib.implicits._

尽可能简单地使用它们。

我目前看到的实现此目的的唯一方法是将我的所有隐式值类定义放在单个源文件中的单个package object(或object)中:

package mylib
package object implicits {

  implicit final class RichValue[T](private val v: Value[T])
  extends AnyVal {
    // ...
  }

  // Etc. with hundreds of other such classes.
}

但是,这远非理想,我更愿意镜像目标库的包结构,但仍通过单个 import 语句将所有内容纳入范围。

是否有一种直接的方法来实现这一点,而不会牺牲这种方法的任何好处?

(例如,我知道如果我放弃制作这些包装器值类,那么我可以在多个不同的traits 中定义它们 - 每个组件包一个 - 并拥有我的root package object 扩展所有这些,通过一次导入将所有内容纳入范围,但我不想为了方便而牺牲性能。)

【问题讨论】:

    标签: scala value-class implicit-class


    【解决方案1】:
    implicit final class RichValue[T](private val v: Value[T]) extends AnyVal
    

    本质上是以下两个定义的语法糖

    import scala.language.implicitConversions // or use a compiler flag
    
    final class RichValue[T](private val v: Value[T]) extends AnyVal
    @inline implicit def RichValue[T](v: Value[T]): RichValue[T] = new RichValue(v)
    

    (您可能会看到,这就是为什么隐式类必须位于特征、对象或类中的原因:它们也有匹配的def

    没有什么需要这两个定义共同存在的。您可以将它们放入单独的对象中:

    object wrappedLibValues {
      final class RichValue[T](private val v: Value[T]) extends AnyVal {
        // lots of implementation code here
      }
    }
    
    object implicits {
      @inline implicit def RichValue[T](v: Value[T]): wrappedLibValues.RichValue[T] = new wrappedLibValues.RichValue(v)
    }
    

    或进入特质:

    object wrappedLibValues {
      final class RichValue[T](private val v: Value[T]) extends AnyVal {
        // implementation here
      }
    
      trait Conversions {
        @inline implicit def RichValue[T](v: Value[T]): RichValue[T] = new RichValue(v)
      }
    }
    
    object implicits extends wrappedLibValues.Conversions
    

    【讨论】:

    • 我不确定这是否像我希望的那样方便。 :-) 根据SIP-13 (implicit classes),隐式转换函数不是自动@inlined - 你从哪里得到的?此外,通过显式创建包装类的实例——即使@inlined——你仍然可以获得性能优势吗? SIP-15 (value classes) 建议不要。我想可能需要进行一些基准测试......
    • @MikeAllen 是的,我在内联上错了(虽然它没有伤害)。是的,通过显式创建实例,您仍然可以获得好处。例如。 here-Xprint:jvm 编译器选项可以看到调用转换为 Foo.printInt$extension
    • 好的,我想这是有道理的。我接受了你的回答,但是——事实上——我不再有隐式值类,而是隐式转换函数值类,因此,每个包装器的解决方案都更加冗长,并且可能容易出错。 :-( 但是,至少我可以更好地组织我的代码。:-) 我想在此基础上更好的解决方案是使用宏来扩展类声明......
    猜你喜欢
    • 1970-01-01
    • 2011-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多