15

Looking at some scala-docs of my libraries, it appeared to me that there is some unwanted noise from value classes. For example:

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

This introduces an unwanted symbol i:

4.i   // arghh....

That stuff appears both in the scala docs and in the IDE auto completion which is really not good.

So... any ideas of how to mitigate this problem? I mean you can use RichInt(val self: Int) but that doesn't make it any better (4.self, wth?)


EDIT:

In the following example, does the compiler erase the intermediate object, or not?

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
}
4

5 回答 5

5

它确实会引入噪音(注意:在 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) 显式隐式模式

有时,您在内部希望您的值被命名为比原始类型更短或更易记的名称,repr或者underlying不使其在原始类型上可用。一种选择是创建一个转发隐式,如下所示:

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.

于 2013-07-30T18:57:49.990 回答
5

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

implicit class RichInt(private val i: Int) extends AnyVal {
  def squared = i * i
}
于 2015-06-30T08:54:39.873 回答
3

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

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

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

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

我觉得前两个和后两个是有区别的。在第一种情况下,值类本身应该是透明的。您不会期望在任何地方都有 type RichInt,但您只会真正操作Int。在第二种情况下,例如4.meters,我知道获得实际的“价值”是有道理的,因此需要 aval是可以的。

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

 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 可以访问的中间表示也可以由编译器完成,但不应在用户编写的代码中可见。(即,如果您想访问对等方,可以使用 a ,但不必使用)。val

于 2013-07-30T16:37:58.783 回答
2

一种可能是使用被遮蔽的名称:

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.

于 2013-07-30T17:48:19.547 回答
0

我不确定这是“不需要的噪音”,因为我认为您在使用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
于 2013-07-30T15:03:48.387 回答