Я обнаружил, что использую Java SimpleDateFormat для создания путей в s3, они содержат даты, которые должны быть отформатированы определенным образом, чтобы соответствовать нашим файловым схемам s3.

Я создал класс, который содержит строку (обычно хэш схемы данных искры) и дату:

case class SchemaName(s: String, d: Date)

очевидным способом было добавить эту функциональность форматирования:

case class SchemaName(hash: String, d: Date){
  val format = new SimpleDateFormat(“yyyyMMdd”)
  def toFormattedString = s”${format.parse(d)}$hash” 
}

Но я не хотел создавать сильную зависимость между моими моделями и их строковым представлением и искал что-то, что будет достаточно гибким для повторного использования во многих местах.

Вместо этого мы создадим классы типов.
цель состоит в том, чтобы добавить определение toFormattedString в любой класс Case и позволить всем этим экземплярам использовать одинаковое форматирование.
Таким образом, мы можем устраните перекрестную зависимость между модулями, и у нас будет общий способ форматирования строк даты. Пришло время демистифицировать магию.

Мы определяем трейт, который форматирует строки, в настоящее время он содержит только один формат и одну функцию, которая будет иметь доступ к этому формату (и будущим):

trait StringFormatter[T] {
 val flatDateFormat = new SimpleDateFormat(“yyyyMMdd”)
 def toFormattedString(a: T): String
}

у нас есть несколько способов украсить трейты:

implicit object SchemaNameStringFormatter extends StringFormatter[SchemaName] {
 def toFormattedString(sm: SchemaName) = s”${flatDateFormat.format(sm.date)}${sm.hash}”
 }
 def toTimeFormattedString[T](a: T)(implicit ev: StringFormatter[T]) = ev.toFString(a)
 val sm = SchemaName(java.sql.Date.valueOf(“”), “myHashValue”)
 toTimeFormattedString(sm) //20170101myHashValue 

Компилятор может найти SchemaNameStringFormatter в качестве доказательства того, что в SchemaName реализована эта функциональность
и программа успешно компилируется.
Это уже неплохо, мы используем наши старые классы и даем им новую жизнь, и пусть они остаются хорошо типизированными.
Однако мне нужен хороший синтаксис, который эмулирует методы, чтобы мы могли сделать:

val sm = SchemaName(java.sql.Date.valueOf(“”), “myHashValue”)
sm.toTimeFormattedString

поэтому мы определяем неявный класс FormatStringsOps, этот класс будет вызываться неявно каждый раз:
1. в области видимости
2. существует реализация StringFormatter для данного типа. (в этом случае компилятор находит SchemaNameStringFormatter)

implicit class FormatStringsOps[A: StringFormatter](thing: A) {
 def toTimeFormattedString = StringFormatter[A].toFormattedString(thing)
}

поэтому каждое имя схемы неявно оборачивается в FormatStringOps, если оно находится в области видимости, и давайте
используем его метод toTimeFormattedString.

Чтобы это работало, нам нужен объект, который создаст эти классы:

object StringFormatter {
 def apply[T: StringFormatter]: StringFormatter[T] = implicitly
}

это здорово, так зачем нам ключевое слово cryptic implicitly и объект StringFormatter?
Просто FormatStringsOps не скомпилируется без него, причина в том, что трейт не может быть создан таким образом, что вам нужно использовать ключевое слово new, но тогда компилятор будет жаловаться, что FString еще не реализован.

поэтому мы используем сопутствующий объект, который может быть создан таким образом. Отлично, теперь у нас есть синтаксический сахар, но как он работает?

давайте посмотрим на подпись

def implicitly[T](implicit e: T) = e //for summoning implicit values from the nether world` (sic) 

что?
Это просто функция `identity`, но она ищет неявное значение в области видимости и оборачивает его в нашем случае с объектом StringFormatter.
соглашение scala заключается в вызове объект-компаньон такой же, как и черта, поэтому его будет приятно импортировать, но это немного сбивает с толку. это может быть записано как:

object CoStringFormatter {
  def apply[T: StringFormatter]: StringFormatter[T] = StringFormatter[T]
}
implicit class FormatStringsOps[A: StringFormatter](thing: A) {
  def toTimeFormattedString = CoStringFormatter[A].toFString(thing)
}

Итак, теперь у нас есть все, чтобы указать компилятору искать конкретную реализацию.

implicit object SchemaNameStringFormatter extends StringFormatter[SchemaName]

SchemaNameStringFormatter действует как вещь: Параметр для класса FormatStringsOps и давайте воспользуемся этим удобным интерфейсом.

val sm = SchemaName(java.sql.Date.valueOf("01-01-1984"), “weDidIt”)
sm.toTimeFormattedString //19840101weDidIt

Есть исправления или вопросы? Я хотел бы услышать об этом в комментариях.