Кодирование с использованием Scala в стиле

Существуют ли руководства по стилю, которые описывают, как писать код, используя импликации Scala?

Последствия действительно мощные, и поэтому ими можно легко злоупотреблять. Существуют ли общие рекомендации, в которых говорится, когда имплициты уместны, а при их использовании скрывает код?

3 ответа

Решение

Я не думаю, что пока есть стиль для всего сообщества. Я видел много соглашений. Я опишу мое и объясню, почему я его использую.

Именование

Я называю мои неявные преобразования одним из

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-библиотека

Я чаще всего использую неявные преобразования для конструкций в стиле 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 преобразование или что-то подобное. Например:

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"

Неявные параметры

Используйте их для:

  • предоставить экземпляры классов типов (например, скаляр)
  • ввести что-то очевидное (например, предоставление ExecutorService какой-то рабочий вызов)
  • в качестве версии внедрения зависимости (например, распространение установки полей типа обслуживания в экземплярах)

Не используйте для лени!

Этот настолько малоизвестен, что ему еще предстоит дать имя (насколько мне известно), но он уже прочно утвердился как один из моих личных фаворитов.

Так что я собираюсь выйти на конечность и назовите ее "шаблонpimp my type class". Возможно, сообщество придумает что-то лучшее.

Это шаблон из трех частей, построенный полностью из последствий. Он также уже используется в стандартной библиотеке (начиная с версии 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 класса type с операциями

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))
  }
}
Другие вопросы по тегам