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

dsg*_*dsg 16 coding-style scala implicit

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

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

Rex*_*err 15

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

命名

我称之为隐式转换之一

implicit def whatwehave_to_whatwegenerate
implicit def whatwehave_whatitcando
implicit def whatwecandowith_whatwehave
Run Code Online (Sandbox Code Playgroud)

我不认为这些是明确使用的,所以我倾向于使用相当长的名字.不幸的是,类名中经常有数字,所以whatwehave2whatwegenerate会议变得混乱.例如:tuple22myclass--is即Tuple2Tuple22你在说什么?

如果隐式转换是从转换的参数和结果中定义的,我总是使用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)
Run Code Online (Sandbox Code Playgroud)

我使用类名和隐式作为一种关于代码点的注释 - 即向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!
Run Code Online (Sandbox Code Playgroud)

现在,一个性能损失支付隐式转换,所以我不热点编写代码这种方式.但除此之外,我有可能使用pimp-my-library模式而不是def,一旦我超越了相关代码中的一些用法.

还有一个考虑因素,即工具不可靠,但在显示隐式转换的来源时,方法来自何处.因此,如果我正在编写难以编写的代码,并且我希望任何使用或维护代码的人都必须很难学习它需要什么以及它是如何工作的,我 - 这几乎是从典型的Java理念 - 更有可能以这种方式使用PML来使步骤对受过训练的用户更加透明.评论将警告说,需要深入理解代码; 一旦你深刻理解,这些变化有助于而不是伤害.另一方面,如果代码执行相对简单的操作,我更有可能留下defs,因为如果我们需要进行更改,IDE将帮助我或其他人快速加快速度.

避免显式转换

我尽量避免明确的转换.你当然可以写

implicit def string_to_int(s: String) = s.toInt
Run Code Online (Sandbox Code Playgroud)

但是它非常危险,即使你似乎正在用.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
}
Run Code Online (Sandbox Code Playgroud)

并且自动地或者在最坏的情况下通过添加类型注释(例如x: String)来获取我开始的任何类.原因是这使得包装类具有最小的侵入性.你真的不想知道包装器; 你有时需要这个功能.您无法完全避免注意到包装器(例如,您只能在一个方向上修复等于,有时您需要返回到原始类型).但是这通常会让你以最小的麻烦编写代码,这有时候只是做的事情.

隐含参数

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

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

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


oxb*_*kes 6

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

隐含转换

当从隐式转换AB它是不是每一个的情况下A,可以看作是B,通过一个拉皮条做toX转换,或者类似的东西.例如:

val d = "20110513".toDate //YES
val d : Date = "20110513" //NO!
Run Code Online (Sandbox Code Playgroud)

别生气了!用于非常常见的核心库功能,而不是在每个类中为了它而皮条客的东西!

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

隐含参数

使用这些来:

  • 提供类型类实例(如scalaz)
  • 注入明显的东西(比如提供ExecutorService一些工人调用)
  • 作为依赖注入的一个版本(例如,在实例上传播服务类型字段的设置)

不要因为懒惰而使用!


Kev*_*ght 5

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

所以我要在这里走出去,把它命名为" 皮条客我的类型 "模式.也许社区会想出更好的东西.

这是一个由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
  //...
}

//...
Run Code Online (Sandbox Code Playgroud)

第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)
    // ...
  }
}
Run Code Online (Sandbox Code Playgroud)

第3部分 - 具有操作的类型类的Pimp成员

implicit def infixNumericOps[T](x: T)(implicit num: Numeric[T]): Numeric[T]#Ops =
  new num.Ops(x)
Run Code Online (Sandbox Code Playgroud)

然后使用它

def addAnyTwoNumbers[T: Numeric](x: T, y: T) = x + y
Run Code Online (Sandbox Code Playgroud)

完整代码:

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))
  }
}
Run Code Online (Sandbox Code Playgroud)