使用Scala进行编码会影响风格

时间:2011-05-12 21:31:18

标签: scala coding-style implicit

是否有任何样式指南描述如何使用Scala implicits编写代码?

隐含非常强大,因此很容易被滥用。是否有一些一般性的指导方针可以说明何时隐含是合适的,何时使用它们会掩盖代码?

3 个答案:

答案 0 :(得分:15)

我认为还没有社区范围的风格。我见过很多惯例。我将描述我的,并解释我使用它的原因。

命名

我将隐式转换称为

之一
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)。< / p>

用法

皮条客-我的库

我对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来使步骤对受过训练的用户更加透明。评论将警告说,需要深入理解代码;一旦你深刻理解,这些变化有助于而不是伤害。另一方面,如果代码执行相对简单的操作,我更有可能保留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)。原因是这使得包装类具有最小的侵入性。你真的不想知道包装器;你有时需要这个功能。您不能完全避免注意到包装器(例如,您只能在一个方向上修复等于,有时您需要返回到原始类型)。但是这通常会让你以最小的麻烦编写代码,这有时候只是做的事情。

隐式参数

隐含的参数非常混乱。我尽可能使用默认值。但有时你不能,尤其是通用代码。

如果可能的话,我会尝试使隐式参数成为其他方法无法使用的东西。例如,Scala集合库有一个CanBuildFrom类,除了集合方法的隐式参数之外,几乎完全没用。因此,意外串扰的危险很小。

如果这是不可能的 - 例如,如果一个参数需要传递给几个不同的方法,但这样做真的会分散代码的作用(例如,尝试在算术中间进行记录),那么而不是使一个公共类(例如String)成为隐式val,我将它包装在一个标记类中(通常使用隐式转换)。

答案 1 :(得分:6)

我不相信我遇到过任何事情,所以让我们在这里创造吧!一些经验法则:

隐含转化

当隐式地从A转换为B时,每个A都可以被视为B的情况它通过拉平toX转换或类似的东西。例如:

val d = "20110513".toDate //YES
val d : Date = "20110513" //NO!

别生气!用于非常常见的核心库功能,而不是在每个类中用于 pimp 的东西!

val (duration, unit) = 5.seconds      //YES
val b = someRef.isContainedIn(aColl)  //NO!
aColl exists_? aPred                  //NO! - just use "exists"

隐式参数

将这些用于:

  • 提供typeclass个实例(例如scalaz
  • 注入明显的(比如为某些工作者调用提供ExecutorService
  • 作为依赖注入的一个版本(例如,在实例上传播 service-type 字段的设置)

不要用于 laziness'的缘故!

答案 2 :(得分:5)

这一点鲜为人知,它还没有给出一个名字(据我所知),但它已经确定为我个人的最爱之一。

所以我要在这里走出困境,并将其命名为“ pimp my type class ”模式。也许社区会提出更好的东西。

这是一个由3部分构成的图案,完全由隐含构建而成。它也已经在标准库中使用(自2.9起)。这里通过严格删除的Numeric类型解释,这应该是熟悉的。

第1部分 - 创建类型类

trait Numeric[T] {
   def plus(x: T, y: T): T
   def minus(x: T, y: T): T
   def times(x: T, y: T): T
   //...
}

implicit object ShortIsNumeric extends Numeric[Short] {
  def plus(x: Short, y: Short): Short = (x + y).toShort
  def minus(x: Short, y: Short): Short = (x - y).toShort
  def times(x: Short, y: Short): Short = (x * y).toShort
  //...
}

//...

第2部分 - 添加提供中缀操作的嵌套类

trait Numeric[T] {
  // ...

  class Ops(lhs: T) {
    def +(rhs: T) = plus(lhs, rhs)
    def -(rhs: T) = minus(lhs, rhs)
    def *(rhs: T) = times(lhs, rhs)
    // ...
  }
}

第3部分 - 具有操作

的类型类的Pimp成员
implicit def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#Ops =
  new num.Ops(x)

然后使用它

def addAnyTwoNumbers[T: Numeric](x: T, y: T) = x + y

完整代码:

object PimpTypeClass {
  trait Numeric[T] {
    def plus(x: T, y: T): T
    def minus(x: T, y: T): T
    def times(x: T, y: T): T
    class Ops(lhs: T) {
      def +(rhs: T) = plus(lhs, rhs)
      def -(rhs: T) = minus(lhs, rhs)
      def *(rhs: T) = times(lhs, rhs)
    }
  }
  object Numeric {
    implicit object ShortIsNumeric extends Numeric[Short] {
      def plus(x: Short, y: Short): Short = (x + y).toShort
      def minus(x: Short, y: Short): Short = (x - y).toShort
      def times(x: Short, y: Short): Short = (x * y).toShort
    }
    implicit def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#Ops =
      new num.Ops(x)
    def addNumbers[T: Numeric](x: T, y: T) = x + y
  }
}

object PimpTest {
  import PimpTypeClass.Numeric._
  def main(args: Array[String]) {
    val x: Short = 1
    val y: Short = 2
    println(addNumbers(x, y))
  }
}