Как извлечь остаток последовательности в сопоставлении с образцом - PullRequest
5 голосов
/ 26 ноября 2011

Я, очевидно, очень плохо объяснил, что я ищу в своем оригинальном посте, поэтому давайте попробуем это еще раз. То, что я пытаюсь выполнить, - это возможность передать последовательность элементов, извлечь один или несколько элементов, а затем передать REMAINDER этой последовательности другому экстрактору. Обратите внимание, что под последовательностью я подразумеваю последовательность (не обязательно список). В моих предыдущих примерах использовался список в качестве последовательности, и я привел несколько примеров извлечения с использованием cons (: :), но я также мог передать массив, как и мою последовательность.

Мне показалось, что я знаю, как работают сопоставление и извлечение шаблонов, но я могу ошибаться, поэтому, чтобы избежать каких-либо дополнительных комментариев и ссылок на то, как делать сайты сопоставления с образцами, вот мое понимание:

Если я хочу вернуть один элемент из моего экстрактора, я бы определил метод неприменения. Этот метод принимает любой тип, который я выбрал в качестве входного (тип может быть последовательностью ...) и возвращает один необязательный элемент (сам возвращаемый тип может быть последовательностью). Возврат должен быть включен в Some, если я хочу совпадение, или None, если я не хочу. Вот пример, который принимает последовательность в качестве входных данных и возвращает ту же последовательность, заключенную в Some, но только если она содержит все строки. Я вполне мог бы просто вернуть последовательность, завернутую в Some, и больше ничего не делать, но это, кажется, вызывает замешательство у людей. Ключ в том, что если он обернут в Some, он будет совпадать, а если None, то не будет. Просто чтобы быть более понятным, совпадение также не произойдет, если входные данные также не соответствуют типу ввода моих неприменимых методов. Вот мой пример:

object Test {
  // In my original post I just returned the Seq itself just to verify I 
  // had matched but many people commented they didn't understand what I 
  // was trying to do so I've made it a bit more complicated (e.g. match 
  // only if the sequence is a sequence of Strings). Hopefully I don't 
  // screw this up and introduce a bug :)
  def unapply[A](xs: Seq[A]): Option[Seq[String]] = 
    if (xs forall { _.isInstanceOf[String] })
      Some(xs.asInstanceOf[Seq[String]])
    else
      None
}

Используя Список в качестве примера, теперь я могу выполнить следующее:

// This works
def test1(xs: List[_]) = xs match {
  case (s: String) :: Test(rest) =>
    println("s = " + s + ", rest = " + rest)
  case _ =>
    println("no match")
}

test1(List("foo", "bar", "baz"))  // "s = foo, rest = List(bar, baz)"

Моя функция test1 принимает List в качестве входных данных и извлекает голову и хвост, используя cons через шаблон конструктора (например,: :( s, rest)). Затем он использует тип ascription (: String), чтобы убедиться, что заголовок (и) является String. Хвост содержит список («bar», «baz»). Это список, что означает, что это также Seq (последовательность). Затем он передается в качестве входных данных моему экстрактору тестов, который проверяет, что и "bar", и "baz" являются строками, и возвращает список, заключенный в Some. Так как Some возвращается, это считается совпадением (хотя в моем первоначальном посте, где я случайно перепутал unapplySeq с unapply, это не сработало, как ожидалось, но это в сторону ...). Это НЕ то, что я ищу. Это был только пример, демонстрирующий, что Test фактически извлекает Seq в качестве входных данных, как и ожидалось.

Теперь, вот где я в прошлый раз вызвал массовое замешательство, когда по неосторожности использовал unapplySeq вместо unapply в моей статье. После большого беспорядка, пытаясь понять комментарии, которые были отправлены, я наконец понял ошибку. Большое спасибо Дэну за то, что он указал мне правильное направление ...

Но только избегайте путаницы, позвольте мне прояснить мое понимание unapplySeq. Как и unapply, unapplySeq принимает любой аргумент, который я выбрал в качестве входных данных, но вместо возврата одного элемента он возвращает последовательность элементов. Каждый элемент в этой последовательности может затем использоваться для дополнительного сопоставления с образцом. Опять же, чтобы совпадение произошло, тип ввода должен совпадать, и моя возвращенная последовательность должна быть заключена в Some, а не в None. При извлечении последовательности элементов, возвращаемых из unapplySeq, вы можете использовать _ *, чтобы сопоставить любые оставшиеся элементы, которые еще не были сопоставлены.

Хорошо, мой экстрактор принимает последовательность в качестве входных данных и возвращает последовательность (как один элемент) в ответ. Поскольку я хочу вернуть только один элемент в качестве совпадения, мне нужно использовать unapply NOT unapplySeq. Несмотря на то, что в моем случае я возвращаю Seq, я не хочу unapplySeq, потому что я не хочу больше сопоставлять шаблоны с элементами в Seq. Я просто хочу вернуть элементы в виде Seq самостоятельно, чтобы затем передать их в тело моего совпадения. Это звучит странно, но для тех, кто понимает, неприменимы против неприменимы. Надеюсь, это не так.

Так вот, что я хочу сделать. Я хочу взять что-то, что возвращает последовательность (например, List или Array), и я хочу извлечь несколько элементов из этой последовательности, а затем извлечь REMAINDER из элементов (например, _ *) в виде последовательности. Давайте назовем это остатком последовательности. Затем я хочу передать оставшуюся последовательность в качестве входных данных моему экстрактору. Мой экстрактор вернет оставшиеся элементы в виде одного Seq, если это соответствует моим критериям. Просто чтобы быть на 100% ясным. У List (или Array и т. Д.) Будет вызываться экстрактор unapplySeq для создания последовательности элементов. Я извлеку один или несколько из этих элементов, а затем передам то, что осталось в виде последовательности, в мой тестовый экстрактор, который будет использовать unapply (НЕ unapplySeq) для возврата остатка. Если вас это смущает, то, пожалуйста, не комментируйте ...

Вот мои тесты:

// Doesn't compile. Is there a syntax for this?
def test2(xs: Seq[_]) = xs match {
  // Variations tried:
  //   Test(rest) @ _*  - doesn't compile (this one seems reasonable to me)
  //   Test(rest @ _*)  - doesn't compile (would compile if Test had 
  //                      unapplySeq, but in that case would bind List's
  //                      second element to Test as a Seq and then bind 
  //                      rest to that Seq (if all strings) - not what I'm
  //                      looking for...). I though that this might work
  //                      since Scala knows Test has no unapplySeq only 
  //                      unapply so @ _* can be tied to the List not Test
  //   rest @ Test(_*)  - doesn't compile (didn't expect to)
  case List(s: String, Test(rest) @ _*) => 
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

// This works, but messy
def test3(xs: List[_]) = xs match {
  case List(s: String, rest @ _*) if (
    rest match { case Test(rest) => true; case _ => false }
  ) => 
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

Я создал test3 на основе комментариев от Джулиана (спасибо Джулиану ..). Некоторые прокомментировали, что test3 делает то, что я хочу, поэтому они запутались в том, что я ищу. Да, это выполняет то, что я хочу достичь, но я не удовлетворен этим. Пример Даниэля тоже работает (спасибо Даниэлю), но я также не удовлетворен необходимостью создать еще один экстрактор, чтобы разделить вещи, а затем делать встроенные извлечения. Эти решения кажутся слишком большой работой, чтобы выполнить что-то, что мне кажется довольно простым. Я хочу, чтобы test2 работал или знал, что это невозможно сделать таким образом. Ошибка указана из-за неправильного синтаксиса? Я знаю, что rest @ _ * вернет Seq, что можно проверить здесь:

def test4(xs: List[_]) = xs match {
  case List(s: String, rest @ _*) => 
    println(rest.getClass)   // scala.collection.immutable.$colon$colon
  case _ =>
    println("no match")
}

Возвращает cons (: :), который является List, который является Seq. Так как же я могу передать _ * Seq на мой экстрактор и получить привязку возврата к переменной rest?

Обратите внимание, что я также пытался передать varargs моему неприменимому конструктору (например, unapply (xs: A *) ...), но это тоже не будет соответствовать.

Итак, я надеюсь, что теперь ясно, когда я скажу, что хочу извлечь остаток последовательности в сопоставлении с образцом. Я не уверен, как еще я могу это произнести.

Исходя из отличного отзыва от Даниэля, я надеюсь, что у него будет ответ для меня:)

Ответы [ 4 ]

6 голосов
/ 27 ноября 2011

Я бы хотел извлечь первый элемент и передать остаток другому экстрактору.

OK. Твой test1 точно так и делает. first_item :: Extractor(the_rest). Странное поведение, которое вы видите, исходит от вашего Test экстрактора. Поскольку у вас уже был ответ на заданный вами вопрос, и ожидаемое поведение вашего Test кажется вам проблемой с test1, кажется, что вам действительно нужна некоторая помощь с экстракторами.

Итак, прочитайте Объекты-экстракторы , из docs.scala-lang.org и Сопоставление с образцом в Scala (pdf) . Хотя этот PDF-файл содержит пример unapplySeq и предлагает, где вы хотите его использовать, вот несколько дополнительных примеров:

object Sorted {
  def unapply(xs: Seq[Int]) =
    if (xs == xs.sortWith(_ < _)) Some(xs) else None
}

object SortedSeq {
  def unapplySeq(xs: Seq[Int]) =
    if (xs == xs.sortWith(_ < _)) Some(xs) else None
}

Интерактивное:

scala> List(1,2,3,4) match { case Sorted(xs) => Some(xs); case _ => None }
res0: Option[Seq[Int]] = Some(List(1, 2, 3, 4))

scala> List(4,1,2,3) match { case Sorted(xs) => Some(xs); case _ => None }
res1: Option[Seq[Int]] = None

scala> List(4,1,2,3) match { case first :: Sorted(rest) => Some(first, rest); case _ => None }
res2: Option[(Int, Seq[Int])] = Some((4,List(1, 2, 3)))

scala> List(1,2,3,4) match { case SortedSeq(a,b,c,d) => (a,b,c,d) }
res3: (Int, Int, Int, Int) = (1,2,3,4)

scala> List(4,1,2,3) match { case _ :: SortedSeq(a, b, _*) => (a,b) }
res4: (Int, Int) = (1,2)

scala> List(1,2,3,4) match { case SortedSeq(a, rest @ _*) => (a, rest) }
res5: (Int, Seq[Int]) = (1,List(2, 3, 4))

Или, может быть - у меня только слабое подозрение на это, вы не так много сказали - вам не нужна помощь экстрактора, но на самом деле вы хотите краткий способ выразить что-то вроде

scala> List(1,2,3,4) match { case 1 :: xs if (xs match { case Sorted(_) => true; case _ => false }) => xs }
res6: List[Int] = List(2, 3, 4)

Erlang имеет такую ​​функцию (хотя, без этих сумасшедших экстракторов):

example(L=[1|_]) -> examine(L).

, который соответствует одному и тому же аргументу дважды - L, а также [1|_]. В Erlang обе стороны = являются полноценными шаблонами и могут быть чем угодно, и вы можете добавить третий или более шаблонов с большим количеством =. Кажется, что Scala поддерживает только форму L=[1|_], имеющую переменную, а затем полный шаблон.

scala> List(4,1,2,3) match { case xs @ _ :: Sorted(_) => xs }
collection.immutable.::[Int] = List(4, 1, 2, 3)
4 голосов
/ 26 ноября 2011

Ну, самый простой способ это:

case (s: String) :: Test(rest @ _*) =>

Если вам нужно, чтобы это работало над общим Seq, вы можете просто определить экстрактор для отделения головы от хвоста:

object Split {
  def unapply[T](xs: Seq[T]): Option[(T, Seq[T])] = if (xs.nonEmpty) Some(xs.head -> xs.tail) else None
}

А затем используйте его как

case Split(s: String, Test(rest @ _*)) =>

Также обратите внимание, что если бы вы определили unapply вместо unapplySeq, то @ _* не потребовалось бы для шаблона, соответствующего Test.

3 голосов
/ 26 ноября 2011

:: - экстрактор.О том, как это работает (из случайного поиска в Google), см., Например, здесь .

def test1(xs: List[_]) = xs match {
  case s :: rest =>
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

scala> test1(List("a", "b", "c"))
s = a rest = List(b, c)

Я думаю, это то, что вы хотели?

2 голосов
/ 26 ноября 2011

Возиться с этим, кажется, что проблема как-то связана с unapplySeq.

object Test {
  def unapply[A](xs: List[A]): Option[List[A]] = Some(xs)
}    

def test1(xs: List[_]) = xs match {
  case (s: String) :: Test(s2 :: rest) =>
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

test1(List("foo", "bar", "baz"))

производит вывод:

s = foo rest = List(baz)

У меня проблемы с поиском документов на разницу между unapply и unapplySeq.

...