组合多个提取器对象以在一个匹配语句中使用

时间:2018-03-01 20:30:03

标签: scala

是否可以在一个匹配语句中运行多个提取器?

object CoolStuff {
  def unapply(thing: Thing): Option[SomeInfo] = ...
}
object NeatStuff {
  def unapply(thing: Thing): Option[OtherInfo] = ...
}

// is there some syntax similar to this?
thing match {
  case t @ CoolStuff(someInfo) @ NeatStuff(otherInfo) => process(someInfo, otherInfo)
  case _ => // neither Cool nor Neat
}

这里的意图是有两个提取器,我不必做这样的事情:

object CoolNeatStuff {
  def unapply(thing: Thing): Option[(SomeInfo, OtherInfo)] = thing match {
    case CoolStuff(someInfo) => thing match {
      case NeatStuff(otherInfo) => Some(someInfo -> otherInfo)
      case _ => None // Cool, but not Neat
    case _ => None// neither Cool nor Neat
  }
}

3 个答案:

答案 0 :(得分:6)

可以尝试

object ~ {
  def unapply[T](that: T): Option[(T,T)] = Some(that -> that)
}

def too(t: Thing) = t match {
  case CoolStuff(a) ~ NeatStuff(b) => ???
}

答案 1 :(得分:2)

我提出了一个非常类似的解决方案,但我有点太慢了,所以我没有把它作为答案发布。但是,由于@userunknown要求解释它是如何工作的,所以无论如何我都会在这里转储我的类似代码,并添加一些注释。也许有人发现它是cchantep的简约解决方案的一个有价值的补充(看起来...... 书法?由于某种原因,在很好的意义上)。

所以,这是我类似的,美学上不太令人满意的提议:

object && {
  def unapply[A](a: A) = Some((a, a))
}

// added some definitions to make your question-code work
type Thing = String
type SomeInfo = String
type OtherInfo = String

object CoolStuff {
  def unapply(thing: Thing): Option[SomeInfo] = Some(thing.toLowerCase)
}
object NeatStuff {
  def unapply(thing: Thing): Option[OtherInfo] = Some(thing.toUpperCase)
}

def process(a: SomeInfo, b: OtherInfo) = s"[$a, $b]"

val res = "helloworld" match {
  case CoolStuff(someInfo) && NeatStuff(otherInfo) =>
     process(someInfo, otherInfo)
  case _ =>
}

println(res)

打印

[helloworld, HELLOWORLD]

我们的想法是,标识符(特别是cchantep代码中的&&~)可以用作infix operators in patterns。因此,match-case

case CoolStuff(someInfo) && NeatStuff(otherInfo) =>

将被嫁入

case &&(CoolStuff(someInfo), NeatStuff(otherInfo)) => 

然后将调用unapply的{​​{1}}方法方法,它只是复制其输入。

在我的代码中,复制是通过简单的&&实现的。在cchantep的代码中,使用较少的括号来完成:Some((a, a))。箭头Some(t -> t)来自ArrowAssoc,后者又作为->中的隐式转化提供。这只是创建通常在地图中使用的对的快速方法:

Predef

另一句话:注意Map("hello" -> 42, "world" -> 58) 可以多次使用:

&&

所以...我不知道这是对cchantep答案的答案还是扩展评论,但也许有人觉得它很有用。

答案 2 :(得分:1)

对于那些可能会错过有关此魔术实际上如何工作的细节的人,只想扩展@cchantep anf @Andrey Tyukin的答案(注释部分不允许我这样做)。

使用 -Xprint:解析器 选项运行 scalac 会产生一些效果( scalac 2.11.12

def too(t: String) = t match {
    case $tilde(CoolStuff((a @ _)), NeatStuff((b @ _))) => $qmark$qmark$qmark
}

这基本上向您展示了编译器在将源解析为AST时执行的初始步骤。

重要说明此处是Infix Operation PatternsExtractor Patterns中描述了编译器进行此转换的规则。特别是,这允许您使用任何具有unapply方法的对象,例如CoolStuff(a) AndAlso NeatStuff(b)。在先前的答案中,&&~也尽可能被选为,但不是唯一可用的有效标识符

如果运行带有选项 -Xprint: patmat scalac ,这是翻译模式匹配的特殊阶段可以看到与此类似的东西

def too(t: String): Nothing = {
  case <synthetic> val x1: String = t;
  case9(){
    <synthetic> val o13: Option[(String, String)] = main.this.~.unapply[String](x1);
    if (o13.isEmpty.unary_!)
      {
        <synthetic> val p3: String = o13.get._1;
        <synthetic> val p4: String = o13.get._2;
        {
          <synthetic> val o12: Option[String] = main.this.CoolStuff.unapply(p3);
          if (o12.isEmpty.unary_!)
            {
              <synthetic> val o11: Option[String] = main.this.NeatStuff.unapply(p4);
              if (o11.isEmpty.unary_!)
                matchEnd8(scala.this.Predef.???)

此处~.unapply将在输入参数t上调用,这将产生Some((t,t))。元组值将被提取到变量p3p4中。然后,将调用CoolStuff.unapply(p3),如果结果不是None NeatStuff.unapply(p4),则调用a并检查其是否不为空。如果两者都不为空,则根据Variable Patterns bSome将绑定到相应{{1}}内的返回结果。