【问题标题】:Value classes introduce unwanted public methods值类引入了不需要的公共方法
【发布时间】:2013-07-30 10:37:03
【问题描述】:

查看我的库的一些 scala 文档,在我看来,值类有一些不需要的噪音。例如:

implicit class RichInt(val i: Int) extends AnyVal {
  def squared = i * i
}

这引入了一个不需要的符号i

4.i   // arghh....

这些东西出现在 scala 文档和 IDE 自动完成中,这确实不好。

那么...关于如何缓解这个问题的任何想法?我的意思是你可以使用RichInt(val self: Int),但这并不能让它变得更好(4.self,wth?)


编辑

在下面的例子中,编译器是否擦除中间对象?

import language.implicitConversions

object Definition {
  trait IntOps extends Any { def squared: Int }
  implicit private class IntOpsImpl(val i: Int) extends AnyVal with IntOps {
    def squared = i * i
  }
  implicit def IntOps(i: Int): IntOps = new IntOpsImpl(i)  // optimised or not?
}

object Application {
  import Definition._
  // 4.i  -- forbidden
  4.squared
}

【问题讨论】:

  • 我会说让它private 或失去限定符,但显然这对于​​值类是不允许的。所以我想答案是:你不能。
  • 或者更好:4.i.i.i.i.i.i

标签: scala visibility named-parameters value-class


【解决方案1】:

我不确定这是“不需要的噪音”,因为我认为您在使用 RichInt 时几乎总是需要访问基础值。 考虑一下:

// writing ${r} we use a RichInt where an Int is required
scala> def squareMe(r: RichInt) = s"${r} squared is ${r.squared}"
squareMe: (r: RichInt)String

// results are not what we hoped, we wanted "2", not "RichInt@2"
scala> squareMe(2)
res1: String = RichInt@2 squared is 4

// we actually need to access the underlying i
scala> def squareMeRight(r: RichInt) = s"${r.i} squared is ${r.squared}"
squareMe: (r: RichInt)String

此外,如果您有一个添加两个 RichInt 的方法,您将需要再次访问基础值:

scala> implicit class ImplRichInt(val i: Int) extends AnyVal {
     |   def Add(that: ImplRichInt) = new ImplRichInt(i + that) // nope...
     | }
<console>:12: error: overloaded method value + with alternatives:
  (x: Int)Int <and>
  (x: Char)Int <and>
  (x: Short)Int <and>
  (x: Byte)Int
 cannot be applied to (ImplRichInt)
         def Add(that: ImplRichInt) = new ImplRichInt(i + that)
                                                        ^

scala> implicit class ImplRichInt(val i: Int) extends AnyVal {
     |   def Add(that: ImplRichInt) = new ImplRichInt(i + that.i)
     | }
defined class ImplRichInt

scala> 2.Add(4)
res7: ImplRichInt = ImplRichInt@6

【讨论】:

  • 这有点显示出价值类别的精神分裂本质。一方面,这个想法是允许单元标记之类的东西(你的第一个例子)。在那种情况下,您不一定会考虑隐式类。另一方面,它是获得无开销扩展方法的机制。在这种情况下,您希望类是透明的,从不返回 RichInt 类型,因此要求 val 没有意义。
  • @0__ 我想我同意:据我了解,值类并不意味着封装或隐藏它们是值类型之上的薄层这一事实他们包装。另一方面,隐式类旨在允许编译器将一种类型交换为另一种类型(而不关心底层类型)。隐式值类,通过混合这两个属性,看起来确实有点尴尬......
【解决方案2】:

也许问题在于绘制值类的异构场景。来自SIP

• 内联隐式包装器。这些包装器上的方法将被转换为扩展方法。

• 新的数字类,例如无符号整数。这样的类将不再需要装箱开销。所以这类似于 .NET 中的值类。

• 表示度量单位的类。同样,这些类不会产生装箱开销。

我认为第一个和最后两个是有区别的。在第一种情况下,值类本身应该是透明的。您不会期望在任何地方都有RichInt 类型,但您实际上只对Int 进行操作。在第二种情况下,例如4.meters,我知道获得实际的“价值”是有意义的,因此需要 val 是可以的。

这种拆分再次反映在值类的定义中:

 1. C 必须只有一个参数,该参数用 val 标记并且具有公共可访问性。

...

7. C 必须是短暂的。

后者表示没有其他字段等,与第 1 条相矛盾。

class C(val u: U) extends AnyVal

SIP 中唯一使用u 的地方是示例实现(例如def extension$plus($this: Meter, other: Meter) = new Meter($this.underlying + other.underlying));然后在中间表示中,最后才再次被擦除:

new C(e).u ⇒ e

合成方法 IMO 可访问的中间表示也可由编译器完成,但不应在用户编写的代码中可见。 (即,如果您想访问对等点,可以使用val,但不必)。

【讨论】:

    【解决方案3】:

    一种可能是使用带阴影的名称:

    implicit class IntOps(val toInt: Int) extends AnyVal {
      def squared = toInt * toInt
    }
    

    或者

    implicit class IntOps(val toInt: Int) extends AnyVal { ops =>
      import ops.{toInt => value}
      def squared = value * value
    }
    

    这仍然会出现在 scala-docs 中,但至少调用 4.toInt 既不会令人困惑,也不会真正触发 IntOps

    【讨论】:

      【解决方案4】:

      它确实会引入噪音(注意:在 2.10 中,在 2.11 及更高版本中,您只需将 val 声明为私有)。你并不总是想要。但目前就是这样。

      您无法通过遵循私有值类模式来解决问题,因为编译器实际上无法在其末尾看到它是一个值类,因此它通过通用路径。这是字节码:

         12: invokevirtual #24;
                //Method Definition$.IntOps:(I)LDefinition$IntOps;
         15: invokeinterface #30,  1;
                //InterfaceMethod Definition$IntOps.squared:()I
      

      看看第一个如何返回类Definition$IntOps的副本?它是盒装的。

      但这两种模式有效,有点:

      (1) 通用名称模式。

      implicit class RichInt(val repr: Int) extends AnyVal { ... }
      implicit class RichInt(val underlying: Int) extends AnyVal { ... }
      

      使用其中一种。添加i 作为方法很烦人。在没有任何底层内容的情况下添加underlying 并没有那么糟糕——如果你无论如何都试图获得底层价值,你只会遇到它。如果你一遍又一遍地使用同一个名字:

      implicit class RicherInt(val repr: Int) extends AnyVal { def sq = repr * repr }
      implicit class RichestInt(val repr: Int) extends AnyVal { def cu = repr * repr * repr }
      
      scala> scala> 3.cu
      res5: Int = 27
      
      scala> 3.repr
      <console>:10: error: type mismatch;
       found   : Int(3)
       required: ?{def repr: ?}
      Note that implicit conversions are not applicable because they are ambiguous:
       both method RicherInt of type (repr: Int)RicherInt
       and method RichestInt of type (repr: Int)RichestInt
      

      无论如何,名称冲突排序都会解决您的问题。如果你真的想要,你可以创建一个空值类,它的存在只是为了与repr发生冲突。

      (2) 显式隐式模式

      有时您在内部希望将您的值命名为比reprunderlying 更短或更易记的名称,而不使其在原始类型上可用。一种选择是像这样创建一个转发隐式:

      class IntWithPowers(val i: Int) extends AnyVal {
        def sq = i*i
        def cu = i*i*i 
      }
      implicit class EnableIntPowers(val repr: Int) extends AnyVal { 
        def pow = new IntWithPowers(repr)
      }
      

      现在您必须调用3.pow.sq 而不是3.sq--这可能是分割命名空间的好方法!--并且您不必担心原始repr 之外的命名空间污染.

      【讨论】:

      • 好点。关于 (2),请参阅我对基于导入的重命名的简短回答。
      【解决方案5】:

      在 Scala 2.11 中,您可以将 val 设为私有,从而解决此问题:

      implicit class RichInt(private val i: Int) extends AnyVal {
        def squared = i * i
      }
      

      【讨论】:

        猜你喜欢
        • 2015-12-17
        • 2015-04-27
        • 2018-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-09
        • 2013-09-08
        • 1970-01-01
        相关资源
        最近更新 更多