Как кодировать ограничение на формат значений 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

}

Мои намерения здесь:

  1. Составить Name от его естественного представления, то есть String значение
  2. Проверьте, является ли его естественное представление действительным Name во время строительства.
  3. Запретить любую другую конструкцию, кроме как заводским методом apply:(str:Str)Str,
  4. Сделайте конструкцию из ее естественного представления неявной, например val a: Name = "ISBN 978-0-9815316-4-9",
  5. Разложить Name в его части в соответствии с его синтаксическими элементами.
  6. Иметь сообщения об ошибках, такие как:

===

--
^
[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)
  }
}

Отказ от ответственности: я на самом деле не тестировал этот код, он может содержать опечатки

Другие вопросы по тегам