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