Mariusz Prowaźnik

o programowaniu w Javie, Scali i Clojure.


Extractor story, czyli znów o Scali

W Scali można w ciekawy sposób tworzyć i używać wyrażenia regularne. Na przykład, wyciągnąć z numeru pesel rok, miesiąc i dzień urodzenia można w taki sposób:

val PeselRegex = """(\d\d)(\d\d)(\d\d)\d{5}""".r
val PeselRegex(year, month, day) = "78111015918"
println(s"Birth date: $day-$month-$year")

Druga linia może przypominać zastosowanie funkcji, lub stworzenie instancji klasy przypadku (ang. case class), ale tak jakby na odwrót – i całkiem słusznie. Btw, jeśli dziwi cię wywołanie .r na obiekcie typu String, zajrzyj do mojego postu o konwersjach niejawnych. W podanym przykładzie, gdy pesel będzie niepoprawny zostanie wyrzucony wyjątek. Jeśli chcemy tego uniknąć, możemy użyć dopasowywania wzorców (ang. pattern matching):

 val PeselRegex = """(\d\d)(\d\d)(\d\d)\d{5}""".r

  "78111015918" match {
    case PeselRegex(year, month, day) => println(s"Birth date: $day-$month-$year")
    case _ => println("Invalid pesel")
  }

W tym przykładzie PeselRegex jeszcze bardziej przypomina klasę przypadku. A to dlatego, że PeselRegex, tak samo jak klasy przypadku, jest ekstraktorem. To znaczy, że implementuje metodę unapply (lub unnaplySeq, ale o tym później). Nic nie stoi na przeszkodzie, by tworzyć własne ekstraktory, przykład

  object WeakPeselExtractor {
    def unapply(pesel: String): Option[(String, String, String)] =
      if (pesel.length == 11 && pesel.forall(_.isDigit))
        Some((pesel.substring(0, 2), pesel.substring(2, 4), pesel.substring(4, 6)))
      else None
  }

  "78111015918" match {
    case WeakPeselExtractor(year, month, day) => println(s"Birth date: $day-$month-$year")
    case _ => println("Invalid pesel")
  }

Zastanawiająca może być ilość wartości. W przypadku WeakPeselExtractor spodziewamy się trzech, ale twórcy klasy Regex przecież nie wiedzieli ile wartości programiści będą chcieli uzyskać tworząc wyrażenia regularne. Ale to nie problem ze dzięki wspomnianej wcześniej metodzie unapplySeq. Poniżej implementacja unapplySeq klasy Regex:

  def unapplySeq(s: CharSequence): Option[List[String]] = {
    val m = pattern matcher s
    if (runMatcher(m)) Some((1 to m.groupCount).toList map m.group)
    else None
  }

Dzięki temu uzyskujemy zmienną ilość parametrów i możemy dopasowywać np tak:

  "some string" match {
    case SomeRegex(a) => ???
    case SomeRegex(a,b) => ???
    case SomeRegex(a,b,c @  _*) => ???
    case _ => ???
  }

Więcej na ten temat:

Brak komentarzy :

Prześlij komentarz