繁体   English   中英

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

[英]Coding with Scala implicits in style

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

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

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

命名

我称之为隐式转换之一

implicit def whatwehave_to_whatwegenerate
implicit def whatwehave_whatitcando
implicit def whatwecandowith_whatwehave

我不认为这些是明确使用的,所以我倾向于使用相当长的名字。 不幸的是,类名中的数字通常已经足够了,所以whatwehave2whatwegenerate会更加容易混淆。 例如: tuple22myclass你正在谈论的是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)

我使用类名和隐式作为一种关于代码点的注释 - 即将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来使步骤对受过训练的用户更加透明。 评论将警告说,需要深入理解代码; 一旦你深刻理解,这些变化有助于而不是伤害。 另一方面,如果代码执行相对简单的操作,我更有可能留下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,我将它包装在一个标记类中(通常带有隐式转换)。

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

隐含转换

当隐式地从A转换为B不是每个A都可以被视为B ,通过toX转换或类似的东西toX 例如:

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

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

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

隐含参数

使用这些来:

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

不要因为懒惰而使用!

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM