簡體   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