Какой смысл неявных преобразований в качестве параметров? - PullRequest
1 голос
/ 11 апреля 2020

Я читаю документы о последствиях в Scala, и есть пример функции с неявным преобразованием в качестве параметра:

def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)

Я понимаю, как это работает , но я не понимаю, какой смысл писать это вместо:

def getIndexExplicit[T](seq: Seq[T], value: T) = seq.indexOf(value)

Насколько я знаю, если существует преобразование из аргумента seq в тип Seq[T], компилятор все равно разрешит вызов getIndexExplicit?

Чтобы проиллюстрировать мою точку зрения, я подготовил этот простой пример:

def equal42[T](a: T)(implicit conv: T => Int) = conv(a) == 42  // implicit parameter version
def equal42Explicit(a: Int) = a == 42                          // just use the type in the signature

implicit def strToInt(str: String): Int = java.lang.Integer.parseInt(str) // define the implicit conversion from String to Int

И действительно, обе функции, похоже, работают одинаково:

scala> equal42("42")
res12: Boolean = true

scala> equal42Explicit("42")
res13: Boolean = true

Если есть нет разницы, какой смысл явно определять неявное преобразование?

Я предполагаю, что в этом простом случае это не имеет значения, но должен быть какой-то более сложный сценарий ios там, где он есть. Что это такое?

1 Ответ

3 голосов
/ 11 апреля 2020

В вашем супер-простом примере:

equal42("42")
equal42Explicit("42")

равно

equal42("42")(strToInt)
equal42Explicit(strToInt("42"))

, что в случае вашего определения не имеет значения.

НО, если оно сделал что-то еще, например

def parseCombined[S, T](s1: S, s2: S)
                       (combine: (S, S) => S)
                       (implicit parse: S => Option[T]): Option[T] =
  parse(combine(s1, s2))

, тогда, когда вы примените преобразование, имеет значение:

implicit def lift[T]: T => Option[T] = t => Option(t)
implicit val parseInt: String => Option[Int] = s => scala.util.Try(s.toInt).toOption
implicit def parseToString[T]: T => Option[String] = t => Option(t.toString)
parseCombined[Option[Int], String]("1", "2") { (a, b) => a.zip(b).map { case (x, y) => x + y } } // Some("Some(3)")
parseCombined[String, Int]("1", "2") { _ + _ } //  Some(12)

В первом случае аргументы были преобразованы перед передачей (а затем снова внутрь), тогда как в другом если они были преобразованы только внутри функции в определенном c месте.

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

При том, что такое использование неявных преобразований является антипаттерном, классы типов будут работать для него гораздо лучше. На самом деле методы расширения являются единственным не спорным использованием неявных преобразований, потому что даже шаблон ma gnet - единственный другой вариант использования, который может использоваться в производстве (см. Akka) - может рассматриваться как проблема.

Так рассматривайте этот пример из документации как демонстрацию механизма, а не как пример хорошей практики, которую следует использовать на производстве.

...