Как кодировать ограничение на формат значений String
Как я часто наблюдаю и как я часто реализую name
атрибут, это просто моделировать его как String
,
Что теперь, если имя должно следовать определенному синтаксису, то есть формату? В Java я, вероятно, определил бы конструктор с проверкой его аргументов, что-то вроде:
public Name(str: String) {
if (str == null) throw new IllegalArgumentException("Str must not be null.");
if (!str.matches("name format expressed as regex")) throw new IllegalArgumentException("Str must match 'regex' but was " + str);
this.str = str;
}
В Scala я придумал следующее решение:
import StdDef.Str
import StdDef.Bol
import StdDef.?
import scala.util.parsing.combinator.RegexParsers
final case class Name private (pfx: ?[Str] = None, sfx: Str) {
override def toString = pfx.mkString + sfx
}
object Name extends RegexParsers {
implicit def apply(str: Str): Name = parseAll(syntax, str) match {
case Success(res, _) => Name(res._1, res._2)
case rej: NoSuccess => error(rej.toString)
}
lazy val syntax = (prefix ?) ~! suffix
lazy val prefix = (("x" | "X") ~! hyph) ^^ { case a ~ b => a + b }
lazy val suffix = alpha ~! (alpha | digit | hyph *) ^^ { case a ~ b => a + b.mkString }
lazy val alpha: Parser[Str] = """\p{Alpha}""".r
lazy val digit: Parser[Str] = """\p{Digit}""".r
lazy val hyph: Parser[Str] = "-"
override lazy val skipWhitespace = false
}
Мои намерения здесь:
- Составить
Name
от его естественного представления, то естьString
значение - Проверьте, является ли его естественное представление действительным
Name
во время строительства. - Запретить любую другую конструкцию, кроме как заводским методом
apply:(str:Str)Str
, - Сделайте конструкцию из ее естественного представления неявной, например
val a: Name = "ISBN 978-0-9815316-4-9"
, - Разложить
Name
в его части в соответствии с его синтаксическими элементами. - Иметь сообщения об ошибках, такие как:
===
--
^
[1.3] error: string matching regex `\p{Alpha}' expected but end of source found
Я хотел бы знать, какие решения вы предлагаете.
После размышления над темой я в настоящее время придерживаюсь следующего подхода.
Token.scala:
abstract class Token {
val value: Str
}
object Token {
def apply[A <: Token](ctor: Str => A, syntax: Regex) = (value: Str) => value match {
case syntax() => ctor(value)
case _ => error("Value must match '" + syntax + "' but was '" + value + "'.")
}
}
Tokens.scala:
final case class Group private (val value: Str) extends Token
final case class Name private (val value: Str) extends Token
trait Tokens {
import foo.{ bar => outer }
val Group = Token(outer.Group, """(?i)[a-z0-9-]++""".r)
val Name = Token(outer.Name, """(?i)(?:x-)?+[a-z0-9-]++""".r)
}
1 ответ
Учитывая то, что вам будет удобно использовать регулярные выражения в Java, кажется излишним пытаться решить ту же проблему с помощью синтаксического анализатора в Scala.
Придерживайтесь того, что вы знаете здесь, но добавьте поворот Scala, чтобы немного очистить решение. Регулярные выражения в Scala также определяют экстракторы, позволяя использовать их в сопоставлении с образцом:
//triple-quote to make escaping easier, the .r makes it a regex
//Note how the value breaks normal naming conventions and starts in uppercase
//This is to avoid backticks when pattern matching
val TestRegex = """xxyyzz""".r
class Name(str: String) {
str match {
case Null => throw new IllegalArgumentException("Str must not be null")
case TestRegex => //do nothing
case _ => throw new IllegalArgumentException(
"Str must match 'regex' but was " + str)
}
}
Отказ от ответственности: я на самом деле не тестировал этот код, он может содержать опечатки