值类引入了不需要的公共方法

时间:2013-07-30 10:20:16

标签: scala visibility named-parameters value-class

查看我的库的一些scala-docs,在我看来,值类中存在一些不需要的噪音。例如:

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

这会引入不需要的符号i

4.i   // arghh....

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

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


修改

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

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
}

5 个答案:

答案 0 :(得分: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

名称碰撞sorta无论如何都要处理你的问题。如果确实想要,您可以创建一个仅存在与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

答案 1 :(得分:5)

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

/mesos

答案 2 :(得分:3)

问题可能是绘制了值类的异构场景。来自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 :(得分: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

答案 4 :(得分: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