Кодирование со стилями Scala в стиле - PullRequest
16 голосов
/ 13 мая 2011

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

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

Ответы [ 3 ]

15 голосов
/ 13 мая 2011

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

Именование

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

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-Library

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

6 голосов
/ 13 мая 2011

Я не верю, что я сталкивался с чем-то, поэтому давайте создадим это здесь! Некоторые практические правила:

Неявные преобразования

При неявном преобразовании из 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"
<Ч />

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

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

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

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

5 голосов
/ 14 мая 2011

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

Так что ясобираюсь выйти здесь на конечность и назвать ее « 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 класса типа с операциями

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))
  }
}
...