Как мне организовать 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