我认为目前还没有社区范围的风格。我见过很多约定。我将描述我的,并解释我为什么使用它。
命名
我将我的隐式转换称为其中之一
implicit def whatwehave_to_whatwegenerate
implicit def whatwehave_whatitcando
implicit def whatwecandowith_whatwehave
我不希望这些被明确使用,所以我倾向于使用相当长的名称。不幸的是,类名中经常有数字,所以whatwehave2whatwegenerate 约定变得混乱。比如:tuple22myclass--你说的是Tuple2还是Tuple22?
如果隐式转换的定义远离转换的参数和结果,我总是使用x_to_y 表示法以获得最大的清晰度。否则,我将名称更多地视为评论。所以,例如,在
class FoldingPair[A,B](t2: (A,B)) {
def fold[Z](f: (A,B) => Z) = f(t2._1, t2._2)
}
implicit def pair_is_foldable[A,B](t2: (A,B)) = new FoldingPair(t2)
我同时使用类名和隐式作为一种注释,说明代码的意义——即在对中添加一个fold 方法(即Tuple2)。
用法
皮条客我的图书馆
对于 pimp-my-library 风格的构造,我最常使用隐式转换。我在所有地方都这样做,它添加了缺少的功能或使生成的代码看起来更干净。
val v = Vector(Vector("This","is","2D" ...
val w = v.updated(2, v(2).updated(5, "Hi")) // Messy!
val w = change(v)(2,5)("Hi") // Okay, better for a few uses
val w = v change (2,5) -> "Hi" // Arguably clearer, and...
val w = v change ((2,5) -> "Hi", (2,6) -> "!")) // extends naturally to this!
现在, 为隐式转换付出了性能损失,所以我不会以这种方式在热点中编写代码。但除此之外,我很可能会使用 pimp-my-library 模式而不是 def,一旦我超过了相关代码中的少数用途。
还有另一个考虑因素,即工具在显示隐式转换的来源方面不如方法的来源可靠。因此,如果我正在编写困难的代码,并且我希望任何使用或维护它的人都必须努力研究它以了解需要什么以及它是如何工作的,我——这几乎是从一种典型的 Java 哲学——更多可能会以这种方式使用 PML 来使步骤对受过训练的用户更加透明。 cmets会提示代码需要深入理解;一旦你深入了解,这些变化会帮助而不是伤害。另一方面,如果代码做的事情相对简单,我更有可能保留 defs,因为如果我们需要进行更改,IDE 将帮助我或其他人快速上手。
避免显式转换
我尽量避免显式转换。你当然可以写
implicit def string_to_int(s: String) = s.toInt
但它非常危险,即使您似乎在用 .toInt 填充所有字符串。
我做的主要例外是包装类。例如,假设您想让一个方法接受具有预先计算的哈希码的类。我会
class Hashed[A](private[Hashed] val a: A) {
override def equals(o: Any) = a == o
override def toString = a.toString
override val hashCode = a.##
}
object Hashed {
implicit def anything_to_hashed[A](a: A) = new Hashed(a)
implicit def hashed_to_anything[A](h: Hashed[A]) = h.a
}
然后自动取回我开始使用的任何类,或者在最坏的情况下,通过添加类型注释(例如x: String)。原因是这使得包装类的侵入性最小。你真的不想知道包装器;您有时只需要该功能。您无法完全避免注意到包装器(例如,您只能在一个方向上修复 equals,有时您需要返回原始类型)。但这通常可以让您轻松编写代码,而这有时只是要做的事情。
隐式参数
隐式参数非常混乱。我尽可能使用默认值。但有时你不能,尤其是通用代码。
如果可能,我会尝试使隐式参数成为其他方法永远不会使用的东西。例如,Scala 集合库有一个CanBuildFrom 类,除了集合方法的隐式参数之外,它几乎完全无用。因此,意外串扰的危险很小。
如果这是不可能的——例如,如果一个参数需要传递给几个不同的方法,但这样做确实会分散代码正在做的事情(例如尝试在算术中间进行日志记录),那么我没有让一个通用类(例如String)成为隐式val,而是将它包装在一个标记类中(通常使用隐式转换)。