如何在模式匹配中提取序列的剩余部分

发布于 2024-12-18 03:48:23 字数 4519 浏览 2 评论 0原文

显然,我在原始帖子中解释我要寻找的内容方面做得非常糟糕,所以让我们再尝试一次。我想要完成的是能够传递一系列项目,提取一个或多个项目,然后将序列的剩余部分传递到另一个提取器。请注意,我所说的序列是指序列(不一定是列表)。我之前的示例使用列表作为序列,并且我给出了一些使用 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
}

以 List 为例,我现在可以执行以下操作:

// 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))。然后它使用类型归属(:String)来确保头部是一个字符串。尾部包含 List("bar", "baz")。这是一个List,这意味着它也是一个Seq(序列)。然后它作为输入传递到我的测试提取器,该提取器验证“bar”和“baz”都是字符串并返回包含在 Some 中的列表。由于返回了 Some,所以它被认为是匹配的(尽管在我原来的帖子中,我无意中将 unapplySeq 与 unapply 混淆了,这并没有按预期工作,但除此之外......)。这不是我要找的。这只是一个例子,表明 Test 实际上确实按照预期提取了 Seq 作为输入。

现在,这就是我上次在写作中无意中使用了 unapplySeq 而不是 unapply 时造成大量混乱的地方。经过一番困惑试图理解所发布的评论后,我终于发现了这个错误。非常感谢 Dan 为我指明了正确的方向...

但为了避免更多的混乱,让我澄清一下我对 unapplySeq 的理解。与 unapply 一样,unapplySeq 接受我选择作为输入的任何参数,但不是返回单个元素,而是返回一个元素序列。此序列中的每个项目都可以用于其他模式匹配。同样,要进行匹配,输入类型必须匹配,并且返回的序列必须包含在 Some 中,而不是 None 中。当提取从 unapplySeq 返回的项目序列时,您可以使用 _* 来匹配任何尚未匹配的剩余项目。

好的,所以我的提取器将一个序列作为输入并返回一个序列(作为单个项目)。由于我只想返回单个项目作为匹配项,因此我需要使用 unapply NOT unapplySeq。即使在我的例子中,我返回一个 Seq,但我不想要 unapplySeq,因为我不想对 Seq 中的项目进行更多模式匹配。我只想将这些项目作为 Seq 单独返回,然后传递到我的案例匹配的正文。这听起来很令人困惑,但对于那些了解 unapply 与 unapplySeq 的人来说,我希望事实并非如此。

这就是我想做的。我想要获取返回序列的东西(例如列表或数组),并且我想从该序列中提取一些项目,然后提取项目的剩余部分(例如_*)作为序列。我们将其称为余数序列。然后我想将余数序列作为输入传递给提取器。如果剩余的项目符合我的条件,我的提取器会将其作为单个 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")
}

我根据 Julian 的评论创建了 test3 (感谢 Julian ..)。有些人评论说 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 呢?

请注意,我还尝试将可变参数传递给我的 unapply 构造函数(例如 unapply(xs: A*)...),但这也不匹配。

因此,当我说我想在模式匹配中提取序列的其余部分时,我希望现在已经很清楚了。我不知道还能用什么语言来表达。

根据丹尼尔的反馈,我希望他能给我一个答案:)

I've obviously done a very poor job of explaining what I'm looking for in my original post so let's try this one more time. What I'm trying to accomplish is the ability to pass a sequence of items, extract one or more of the items, and then pass the REMAINDER of the sequence on to another extractor. Note that by sequence I mean sequence (not necessarily a List). My previous examples used list as the sequence and I gave some examples of extraction using cons (::), but I could just as well pass an Array as my sequence.

I thought I knew how pattern matching and extraction worked but I could be wrong so to avoid any more basic comments and links to how to do pattern matching sites here's my understanding:

If I want to return a single item from my extractor I would define an unapply method. This method takes whatever type I chose as input (the type could be a sequence...) and returns a single optional item (the return type could itself be a sequence). The return must be wrapped in Some if I want a match or None if I don't. Here is an example that takes a sequence as input and returns the same sequence wrapped in Some but only if it contains all Strings. I could very well just return the sequence wrapped in Some and not do anything else, but this seems to cause confusion for people. The key is if it is wrapped in Some then it will match and if it is None it will not. Just to be more clear, the match will also not happen unless the input also matches my unapply methods input type. Here is my example:

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
}

Using List as an example, I can now perform the following:

// 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)"

My test1 function takes List as input and extracts the head and tail using cons via the constructor pattern (e.g. ::(s, rest)). It then uses type ascription (: String) to make sure the head (s) is a String. The tail contains List("bar", "baz"). This is a List which means it is also a Seq (sequence). It is then passed as input to my Test extractor which verifies that both "bar" and "baz" are strings and returns the List wrapped in Some. Since Some is returned it is considered a match (although in my original post where I inadvertently mixed up unapplySeq with unapply this didn't work as expected, but that aside...). This is NOT what I'm looking for. This was only an example to show that Test does in fact extract a Seq as input as expected.

Now, here's where I caused mass confusion last time when I inadvertently used unapplySeq instead of unapply in my write up. After much confusion trying to understand the comments that were posted I finally picked up on the mistake. Many thanks to Dan for pointing me in the right direction...

But just be avoid any more confusion, let me clarify my understanding of unapplySeq. Like unapply, unapplySeq takes in whatever argument I choose as input, but instead of returning a single element it returns a sequence of elements. Each item in this sequence can then be used for additional pattern matching. Again, to make a match happen the input type must match and my returned sequence must be wrapped in Some and not be None. When extracting over the sequence of items returned from unapplySeq, you can use _* to match any remaining items not yet matched.

Ok, so my extractor takes a sequence as input and returns a sequence (as a single item) in return. Since I only want to return a single item as a match I need to use unapply NOT unapplySeq. Even though in my case I'm returning a Seq, I don't want unapplySeq because I don't want to do more pattern matching on the items in the Seq. I just want to return the items as a Seq on its own to then be passed to the body of my case match. This sounds confusing, but to those that understand unapply vs unapplySeq I hope it isn't.

So here is what I WANT to do. I want to take something that returns a sequence (e.g. List or Array) and I want to extract a few items from this sequence and then extract the REMAINDER of the items (e.g. _*) as a sequence. Let's call it the remainder sequence. I want to then pass the remainder sequence as input to my extractor. My extractor will then return the remaining items as a single Seq if it matches my criteria. Just to be 100% clear. The List (or Array, etc) will have its unapplySeq extractor called to create the sequence of items. I will extract a one or more of these items and then pass what is left as a sequence to my Test extractor which will use unapply (NOT unapplySeq) to return the remainder. If you are confused by this, then please don't comment...

Here are my tests:

// 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")
}

I created test3 based on comments from Julian (thanks Julian..). Some have commented that test3 does what I want so they are confused what I'm looking for. Yes, it accomplishes what I want to accomplish, but I'm not satisfied with it. Daniel's example also works (thanks Daniel), but I'm also not satisfied with having to create another extractor to split things and then do embedded extractions. These solutions seem too much work in order to accomplish something that seems fairly straight forward to me. What I WANT is to make test2 work or know that it can't be done this way. Is the error given because the syntax is wrong? I know that rest @ _* will return a Seq, that can be verified here:

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

It returns cons (::) which is a List which is a Seq. So how can I pass the _* Seq on to my extractor and have is return bound to the variable rest?

Note that I've also tried passing varargs to my unapply constructor (e.g. unapply(xs: A*)...) but that won't match either.

So, I hope it is clear now when I say I want to extract the remainder of a sequence in pattern matching. I'm not sure how else I can word it.

Based on the great feedback from Daniel I'm hoping he is going to have an answer for me :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

活雷疯 2024-12-25 03:48:23

我想提取第一个项目并将其余部分传递给另一个提取器。

好的。您的 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)

I'd like to extract the first item and pass the remainder on to another extractor.

OK. Your test1 does that, exactly. first_item :: Extractor(the_rest). The weird behavior you're seeing comes from your Test extractor. As you already had the answer to your stated question, and as expected behavior from your Test strikes you as a problem with test1, it seems that what you really want is some help with extractors.

So, please read Extractor Objects, from docs.scala-lang.org, and Pattern Matching in Scala (pdf). Although that PDF has an example of unapplySeq, and suggests where you'd want to use it, here are some extra examples:

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
}

Interactively:

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))

Or maybe -- I only have the faint suspicion of this, you haven't said as much -- you don't want extractor help, but actually you want a terse way to express something like

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 has a feature like this (although, without these crazy extractors):

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

, which pattern-matches the same argument twice - to L and also to [1|_]. In Erlang both sides of the = are full-fledged patterns and could be anything, and you can add a third or more patterns with more =. Scala seems to only support the L=[1|_] form, having a variable and then a full pattern.

scala> List(4,1,2,3) match { case xs @ _ :: Sorted(_) => xs }
collection.immutable.::[Int] = List(4, 1, 2, 3)
﹉夏雨初晴づ 2024-12-25 03:48:23

好吧,最简单的方法是这样的:

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

如果您需要它在一般的 Seq 上工作,您可以定义一个提取器来将 head 与 tail 分开:

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 @ _*)) =>

另请注意,如果您定义了 < code>unapply 而不是 unapplySeq,则 Test 匹配的模式不需要 @ _*

Well, the easiest way is this:

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

If you need this to work on general Seq, you can just define an extractor to split head from tail:

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

And then use it like

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

Also note that if you had defined unapply instead of unapplySeq, then @ _* would not be required on the pattern matched by Test.

弱骨蛰伏 2024-12-25 03:48:23

:: 是一个提取器。有关其工作原理(通过随机谷歌搜索),请参阅,例如 这里

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)

我想这就是你想要的?

:: is an extractor. For how it works (from a random googling), see, for example, here.

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)

I think this is what you wanted?

嘿哥们儿 2024-12-25 03:48:23

搞砸了,这个问题似乎与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)

我在谷歌搜索有关 unapplyunapplySeq 之间差异的文档时遇到了麻烦。

Messing around with this, it seems that the issue has something to do with 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"))

produces the output:

s = foo rest = List(baz)

I'm havng trouble googling up docs on the difference between unapply and unapplySeq.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文