Что я делаю неправильно, добавляя дополнительный конструктор класса case, который сначала преобразует его параметры?
Итак, у меня был очень простой класс case:
case class StreetSecondary1(designator: String, value: Option[String])
Это работало отлично. Тем не менее, у меня были места, где я разбирал одну строку в кортеж, который затем использовался для создания экземпляра этого класса case:
def parse1(values: String): StreetSecondary1 = {
val index = values.indexOf(" ")
StreetSecondary1.tupled(
if (index > -1)
//clip off string prior to space as designator and optionally use string after space as value
(values.take(index), if (values.size > index + 1) Some(values.drop(index + 1)) else None)
else
//no space, so only designator could have been provided
(values, None)
)
}
Итак, я хотел реорганизовать все разные места с помощью этого же кода синтаксического анализа в класс case, как это (но это не будет компилироваться):
case class StreetSecondary2(designator: String, value: Option[String]) {
def this(values: String) = this.tupled(parse(values))
private def parse(values: String): (String, Option[String]) = {
val index = values.indexOf(" ")
if (index > -1)
//clip off string prior to space as designator and optionally use string after space as value
(values.take(index), if (values.size > index + 1) Some(values.drop(index + 1)) else None)
else
//no space, so only designator could have been provided
(values, None)
}
}
Похоже, что возникает проблема с цыпленком/яйцом при добавлении конструктора класса case И с функцией, которая принимает параметр и преобразует их перед вызовом фактического конструктора. Я возился с этим (идя по многим касательным). Затем я прибег к попытке пути сопутствующего объекта:
object StreetSecondary3 {
private def parse(values: String): (String, Option[String]) = {
val index = values.indexOf(" ")
if (index > -1)
//clip off string prior to space as designator and optionally use string after space as value
(values.take(index), if (values.size > index + 1) Some(values.drop(index + 1)) else None)
else
//no space, so only designator could have been provided
(values, None)
}
def apply(values: String): StreetSecondary3 = {
val tuple: (String, Option[String]) = parse(values)
StreetSecondary3(tuple._1, tuple._2) //Why doesn't .tupled method work here?
}
}
case class StreetSecondary3(designator: String, value: Option[String])
Что я делаю неправильно в StreetSecondary2? Есть ли способ заставить его работать? Разумеется, должен быть более простой способ, когда мне не нужно добавлять все шаблоны сопутствующих объектов, присутствующие в StreetSecondary3. Есть?
Спасибо за любые отзывы и рекомендации, которые вы можете мне дать.
ОБНОВЛЕНИЕ
Уф! Уже много уроков.
A) метод parse StreetSecondary2 не использует неявный контекст "this" в создаваемом экземпляре класса case (т.е. является статическим методом в терминах Java), поэтому он лучше перемещается в объект-компаньон.
B) К сожалению, при составлении явного сопутствующего объекта для класса case компилятор, предоставляющий "неявный сопутствующий объект", теряется. Метод пополнения (и другие, я угадываю - конечно, пожелал, чтобы был способ сохранить его и увеличить, в отличие от его удаления) содержались в компиляторе, снабженном "неявным сопутствующим объектом" и не предоставляемом в новом явном сопутствующем объекте. Это было исправлено добавлением "extends ((String, Option [String]) = > StreetSecondary) к явному сопутствующему объекту.
C) Здесь обновленное решение (которое также включает более краткий вариант функции синтаксического анализа с благодарностью Габриэле Петронелле):
object StreetSecondary4 extends ((String, Option[String]) => StreetSecondary4) {
private def parseToTuple(values: String): (String, Option[String]) = {
val (designator, value) = values.span(_ != ' ')
(designator, Option(value.trim).filter(_.nonEmpty))
}
def apply(values: String): StreetSecondary4 =
StreetSecondary4.tupled(parseToTuple(values))
}
case class StreetSecondary4(designator: String, value: Option[String])
Это едва лучше с точки зрения шаблона, чем версия StreetSecondary3. Тем не менее, теперь это становится немного более ощутимым из-за того, что явный контекстный контекст стал явным.
Ответы
Ответ 1
Вы нажимаете ограничение языка на вспомогательные конструкторы
В Scala каждый вспомогательный конструктор должен вызывать другой конструктор того же класса, что и его первое действие. Другими словами, первый оператор в каждом вспомогательном конструкторе в каждом классе Scala будет иметь вид this (...). Вызываемый конструктор является либо основным конструктором (как в примере Rational), либо другим вспомогательным конструктором, который идет текстовым образом перед вызывающим конструктором. Чистый эффект этого правила заключается в том, что каждый вызов конструктора в Scala в конечном итоге вызовет основной конструктор класса. Таким образом, основной конструктор является единственной точкой входа класса.
Если вы знакомы с Java, вы можете задаться вопросом, почему правила Scala s для конструкторов немного более строгие, чем Javas. В Java конструктор должен либо вызывать другой конструктор того же класса, либо непосредственно вызывать конструктор суперкласса в качестве своего первого действия. В классе Scala только основной конструктор может вызывать конструктор суперкласса. Увеличенное ограничение в Scala представляет собой конструктивный компромисс, который нужно было заплатить в обмен на большую лаконичность и простоту конструкторов Scala по сравнению с Javas.
(Программирование в Scala, глава 6, раздел 7)
Поэтому вы не можете вызывать this.tupled
как первый оператор вашего вспомогательного конструктора.
Добавление конструктора в объект-компаньон - это, безусловно, способ пойти в этом случае.
Не относится к самому вопросу, но вы можете значительно упростить свой метод parse
, используя span
private def parse(values: String): (String, Option[String]) = {
val (designator, value) = values.span(_ != ' ')
(designator, Option(value.trim).filter(_.nonEmpty))
} // ^^^^^^^^^^^^^^^^^^
// returns None if the string is empty
Ответ 2
В этом случае я бы использовал объект trait и companion.
trait StreetSecondary1 {
def designator: String
def value: Option[String]
}
object StreetSecondary1 {
// For `StreetSecondary1("str", Some("val"))`
def apply(d: String, v: Option[String]): StreetSecondary1 =
new StreetSecondary1 {
val designator = d
val value = v
}
// For `StreetSecondary1("raw")
def apply(raw: String): StreetSecondary1 = {
val (d, v) = parse(raw)
apply(d, v)
}
private def parse(values: String): (String, Option[String]) = {
val index = values.indexOf(" ")
if (index > -1)
//clip off string prior to space as designator and optionally use string after space as value
(values.take(index), if (values.size > index + 1) Some(values.drop(index + 1)) else None)
else
//no space, so only designator could have been provided
(values, None)
}
}