Скала Неявное Преобразование Gotchas - PullRequest
2 голосов
/ 01 апреля 2012

РЕДАКТИРОВАТЬ
ОК, @Drexin поднимает хороший вопрос: потеря безопасности типов / неожиданные результаты при использовании неявных преобразователей.

Как насчет менее распространенного преобразования, гдеконфликты с PreDef имплицитами не возникнут?Например, я работаю с JodaTime (отличный проект!) В Scala.В том же объекте пакета контроллера, где определены мои импликации, у меня есть псевдоним типа:

type JodaTime = org.joda.time.DateTime

и неявный, который преобразует JodaTime в Long (для DAL, построенного поверх ScalaQuery, где даты хранятся как Long)

implicit def joda2Long(d: JodaTime) = d.getMillis

Здесь не может быть двусмысленности между импликациями PreDef и моего пакета контроллера, и имплицитные контроллеры не будут фильтроваться в DAL, как это происходит в другой области пакета.Поэтому, когда я выполняю

dao.getHeadlines(articleType, Some(jodaDate))

, для меня, IMO, безопасно выполняется неявное преобразование в Long, и, учитывая, что запросы, основанные на дате, интенсивно используются, я сохраняю несколько шаблонов.

Аналогичнодля преобразований str2Int уровень контроллера получает параметры URI сервлета в виде String -> String.Во многих случаях URI тогда содержит числовые строки, поэтому, когда я фильтрую маршрут, чтобы определить, является ли String типом Int, я не хочу каждый раз stringVal.toInt;вместо этого, если регулярное выражение проходит, позвольте неявному преобразовать строковое значение в Int для меня.Все вместе это выглядело бы так:

implicit def str2Int(s: String) = s.toInt
get( """/([0-9]+)""".r ) {
  show(captures(0)) // captures(0) is String
}
def show(id: Int) = {...}

В приведенных выше контекстах, являются ли эти допустимые варианты использования для неявных преобразований или же, более того, всегда явными?Если последнее, то каковы допустимые варианты использования неявного преобразования?

ORIGINAL
В объекте пакета у меня есть некоторые неявные преобразованияопределены, одна из них простая строка в Int:

implicit def str2Int(s: String) = s.toInt

Как правило, это работает нормально, методы, которые принимают параметр Int, но получают строку, делают преобразование в Int, как и методы, в которых тип возвращаемого значенияимеет значение Int, но фактическое возвращаемое значение - String.

Отлично, теперь в некоторых случаях ошибки компилятора с неявной страшной неявностью:

оба метода augmentString в объекте Predefтипа (x: String) scala.collection.immutable.StringOps и метод str2Int (s: String) Int являются возможными функциями преобразования из java.lang.String в? {val toInt:?}

Я знаю, что это происходит, когда я пытаюсь выполнить ручные встроенные преобразования строки в Int.Например, val i = "10".toInt

Мой обходной путь / хак был в том, чтобы создать помощника asInt вместе с последствиями в объекте пакета: def asInt(i: Int) = i и использовать как, asInt("10")

Итак,Является ли неявная лучшая практика неявной (т.е. учиться, будучи обожженной), или есть какие-то руководящие принципы, которым нужно следовать, чтобы не попасть в ловушку собственного производства?Другими словами, следует ли избегать простых общих неявных преобразований и использовать только те, где тип для преобразования уникален?(т.е. никогда не попадет в ловушку неоднозначности)

Спасибо за обратную связь, последствия потрясающие ... когда они работают как положено; -)

Ответы [ 2 ]

6 голосов
/ 01 апреля 2012

Я думаю, что вы смешиваете два разных варианта использования.

В первом случае вы используете неявные преобразования, используемые для сокрытия произвольного различия (или, в любом случае, произвольного для вас) между различнымизанятия в случаях, когда функциональность идентична.Неявное преобразование JodaTime в Long вписывается в эту категорию;это, вероятно, безопасно, и очень вероятно, хорошая идея.Я, вероятно, вместо этого использовал бы шаблон enrich-my-library и написал бы

class JodaGivesMS(jt: JodaTime) { def ms = jt.getMillis }
implicit def joda_can_give_ms(jt: JodaTime) = new JodaGivesMS(jt)

и использовал бы .ms при каждом вызове, просто чтобы быть явным.Причина в том, что здесь имеют значение единицы измерения (миллисекунды не являются микросекундами, не являются секундами и не являются миллиметрами, но все могут быть представлены в виде целых чисел), и я бы предпочел оставить некоторую запись о том, какие единицы измерения находятся на интерфейсе., в большинстве случаев.getMillis - это скорее полный набор текста каждый раз, но ms не так уж и плох.Тем не менее, преобразование является разумным (если хорошо документировано для людей, которые могут изменить код в ближайшие годы (включая вас)).

Однако во втором случае вы выполняете ненадежное преобразование между однимочень распространенный тип и другое.Правда, вы делаете это только в ограниченном контексте, но это преобразование все еще может ускользнуть и вызвать проблемы (исключения или типы, которые не соответствуют вашим ожиданиям).Вместо этого вы должны написать те удобные подпрограммы, которые вам нужны, чтобы правильно обрабатывать преобразование, и использовать их везде.Например, предположим, у вас есть поле, которое вы ожидаете, чтобы быть «да», «нет» или целое число.У вас может быть что-то вроде

val Rint = """(\d+)""".r
s match {
  case "yes" => println("Joy!")
  case "no" => println("Woe!")
  case Rint(i) => println("The magic number is "+i.toInt)
  case _ => println("I cannot begin to describe how calamitous this is")
}

Но этот код неверен , потому что "12414321431243".toInt выдает исключение, когда вы действительно хотите сказать, что ситуация ужасна.Вместо этого вы должны написать код, который соответствует правильно:

case object Rint {
  val Reg = """([-]\d+)""".r
  def unapply(s: String): Option[Int] = s match {
    case Reg(si) =>
      try { Some(si.toInt) }
      catch { case nfe: NumberFormatException => None }
    case _ => None
  }
}

и использовать его вместо этого.Теперь вместо выполнения рискованного и неявного преобразования из String в Int, когда вы выполняете сопоставление, все будет обрабатываться должным образом, оба совпадения с регулярным выражением (чтобы избежать выбрасывания и отлова куч исключений наплохие разборы) и обработка исключений, даже если регулярное выражение проходит.

Если у вас есть что-то, имеющее как строковое, так и представление int, создайте новый класс, а затем сделайте неявные преобразования для каждого, если вы не хотитеиспользование вами объекта (который, как вы знаете, может быть безопасным) для повторения вызова метода, который на самом деле не дает никакого освещения.

5 голосов
/ 01 апреля 2012

Я стараюсь не преобразовывать что-либо неявно, просто чтобы преобразовать это из одного типа в другой, но только для шаблона моей библиотеки для сутенера.Это может немного сбить с толку, когда вы передаете String функции, которая принимает Int.Также существует огромная потеря безопасности типа.Если вы передадите строку в функцию, которая по ошибке принимает Int, компилятор не сможет ее обнаружить, поскольку он предполагает, что вы хотите это сделать.Поэтому всегда делайте преобразование типов явно и используйте только неявные преобразования для расширения классов.

edit:

Чтобы ответить на ваш обновленный вопрос: Для удобства чтения используйте явный getMillis.В моих глазах допустимыми вариантами использования для имплицитов являются «pimp my library», границы представления / контекста, классы типов, манифесты, компоновщики ... но не лень писать явный вызов метода.

...