Как мне организовать implicits в моем приложении Scala?
Написав несколько инструментов scala, я пытаюсь справиться с лучшим способом упорядочить свой код - особенно подразумевается. У меня есть 2 цели:
- Иногда я хочу иметь возможность импортировать только те запросы, которые я запрашиваю.
- В других случаях я хочу просто импортировать все.
Чтобы избежать дублирования имплицитов, я придумал эту структуру (аналогично тому, как устроен scalaz):
case class StringW(s : String) {
def contrived = s + "?"
}
trait StringWImplicits {
implicit def To(s : String) = StringW(s)
implicit def From(sw : StringW) = sw.s
}
object StringW extends StringWImplicits
// Elsewhere on Monkey Island
object World extends StringWImplicits with ListWImplicits with MoreImplicits
Это позволяет мне просто
import StringW._ // Selective import
или (в большинстве случаев)
import World._. // Import everything
Как все это делают?
Ответы
Ответ 1
Я думаю, что конверсии implicit
опасны, если вы не знаете, откуда они. В моем случае я помещаю свой implicit
в класс Conversions
и import
его как можно ближе к использованию
def someMethod(d: Date) ; Unit {
import mydate.Conversions._
val tz = TimeZone.getDefault
val timeOfDay = d.getTimeOfDay(tz) //implicit used here
...
}
Я не уверен, что мне нравится "наследование" implicits от различных trait
по той же причине, что считалось плохой практикой Java реализовать interface
, чтобы вы могли напрямую использовать свои константы ( предпочтительным является статический импорт).
Ответ 2
Обычно у меня было implicit
преобразований в объекте, который ясно указывает, что импортируемое является преобразованием implicit
.
Например, если у меня есть класс com.foo.bar.FilthyRichString
, неявные преобразования перейдут в com.foo.bar.implicit.FilthyRichStringImplicit
. Я знаю, что имена немного длинны, но почему у нас есть IDE (и поддержка Scala IDE улучшается). То, как я это делаю, заключается в том, что я считаю важным, чтобы все неявные преобразования можно было четко увидеть в 10-секундном обзоре кода. Я мог бы посмотреть на следующий код:
// other imports
import com.foo.bar.FilthyRichString
import com.foo.bar.util.Logger
import com.foo.bar.util.FileIO
import com.foo.bar.implicits.FilthyRichStringImplicit._
import com.foo.bar.implicits.MyListImplicit._
// other implicits
и с первого взгляда увидеть все неявные преобразования, которые активны в этом исходном файле. Они также будут собраны вместе, если вы используете соглашение о том, что импорт сгруппирован по пакетам, с новой строкой между различными пакетами.
Вдоль строк одного и того же аргумента мне не нужен объект catch-all, который содержит все неявные преобразования. В большом проекте вы бы действительно использовали все неявные преобразования во всех ваших исходных файлах? Я думаю, что это означает очень тугое соединение между различными частями вашего кода.
Кроме того, для документации не очень хорош объект catch-all. В случае явного написания всех неявных преобразований, используемых в файле, можно просто взглянуть на свои операторы импорта и сразу перейти к документации неявного класса. В случае объекта catch-all нужно будет посмотреть на этот объект (который в большом проекте может быть огромным), а затем искать неявное преобразование, которое они выполняют после.
Я согласен с oxbow_lakes, что наличие неявного преобразования в trait
является плохим из-за соблазна унаследовать от него, что, по его словам, является плохой практикой. Вдоль этих строк я бы сделал объекты, содержащие неявные преобразования final
, чтобы избежать соблазна в целом. Его идея импортировать их как можно ближе к использованию, также очень хороша, если неявные преобразования просто используются экономно в коде.
-- Flaviu Cipcigan