在子类中扩展Scala模式匹配,同时保持复杂性

时间:2012-02-08 13:40:33

标签: scala pattern-matching complexity-theory

我有一个Scala问题。想象一下,你正在构建代码来处理不同的操作,即

operation match {
   case A => doA()
   case B => doB()
   case _ => throw new Exception("Unknown op: " + operation)
}

现在,想象一下,稍后你想要构建一个新版本,你想扩展该模式匹配操作C.你怎么能以操作分辨率仍为O(1)的方式来做?

我的意思是,我可以修改上面的代码:

   case _ => handleUnknownOperation(operation)

子类可以实现handleUnknownOperation来执行:

operation match {
   case C => doC()
}

但这很糟糕,因为它意味着C操作需要O(2)。

任何其他想法或扩展此类模式匹配结构的最佳实践?

干杯, 盖尔德

3 个答案:

答案 0 :(得分:4)

为了回答原始问题,模式匹配被有效地转换为一系列if / else语句,只是一系列测试。最后的case _只是一个漏洞(没有相关的测试)。因此,在单个匹配中使用A,B和C以及在匹配中使用A和B,然后委托给匹配C的另一个匹配项之间几乎没有区别。使用以下示例:

class T

case class A(v: Int) extends T
case class B(v: Int) extends T
case class C(v: Int) extends T

class Foo {
  def getOperation(t: T): Unit = {
    t match {
      case A(2) => println("A")
      case B(i) => println("B")
      case _ => unknownOperation(t)
    }
  }

  def unknownOperation(t: T): Unit = println("unknown operation t=" + t)
}

class Bar extends Foo {
  override def unknownOperation(t: T): Unit = t match {
    case C(i) => println("C")
    case _ => println("unknown operation t=" + t)
  }
}

使用jad反编译Foo.class,我们得到:

public void getOperation(T t) {
label0:
    {
        T t1 = t;
        if(t1 instanceof A)
        {
            if(((A)t1).v() == 2)
            {
                Predef$.MODULE$.println("A");
                break label0;
            }
        } else
        if(t1 instanceof B)
        {
            Predef$.MODULE$.println("B");
            break label0;
        }
        unknownOperation(t);
    }
}

所以,我会说你不应该担心表现[*]。

但是,从设计的角度来看,我可能会稍微改变一下并使用Frank建议的Command模式,或者不是覆盖子类中的unknownOperation,而是覆盖getOperation ,它与C匹配,然后委托给super.getOperation(),这似乎更适合我。

class Bar extends Foo {
  override def getOperation(t: T): Unit = t match {
    case C(i) => println("C")
    case _ => super.getOperation(t)
  }
}

[*]对此的警告将是复杂性。在2.10之前版本的Scala中存在一个问题,其中模式匹配器生成过于复杂的.class文件(nested extractors generate exponential-space bytecode),因此如果您使用的是非常复杂的匹配,则可能会导致问题。这在2.10中使用虚拟模式匹配器修复。如果您遇到此问题,则此错误的解决方法之一是将模式匹配拆分为不同的方法/类。

答案 1 :(得分:2)

re:handleUnknownOperation方法,这不是一个非常干净的OO设计。例如,如果要在v3子类中处理D操作,则会变得有点奇怪。相反,只需创建一个子类可以覆盖的handle方法:

class BaseProtocol {
  def handle(operation: Operation) = operation match {
    case A => doA()
    case B => doB()
    case _ => throw new Exception("Unknown op: " + operation)
  }
}

class DerivedProtocol extends BaseProtocol {
  override def handle(operation: Operation) = operation match {
    case C => doC()
    case _ => super.handle(operation)
  }
}

re:效率,你可能过早地优化,但我不会让那阻止我: - )

你的所有operation object都是?如果是这样,您可以将match语句替换为Map

class BaseProtocol {
  def handlers = Map(A -> doA _, B -> doB _)

  def handle(operation: Operation) =
    handlers.get(operation)
      .getOrElse(throw new Exception("Unknown op: " + operation))
      .apply()
}

class DerivedProtocol extends BaseProtocol {
  override def handlers = super.handlers + (C -> doC _)
}

答案 2 :(得分:0)

根据我上面的评论,我从设计角度而不是从绩效角度来考虑这个问题。

所以问题稍微转移到:如何编写支持多种可能操作的协议实现,并将其设置为可版本化以便可以调整操作查找?

在子类化的评论中给出了一种解决方案的方法。为了保持操作解析机制不变,我甚至建议用另一种查找机制替换模式匹配。

这是一个想法:

  • 基类/特质CanPerformOperations。这个类保留了一个哈希映射,它映射操作(看起来你正在使用案例object,这可以很好地工作)到执行实际操作的函数。它还提供了注册操作的方法,即修改哈希映射,并通过在哈希映射中查找操作函数并执行它来执行与命令相关的操作。
  • ProtocolVersion1 extends CanPerformOperations分别为doAdoB命令注册操作AB
  • ProtocolVersion2 extends ProtocolVersion1还为doC命令注册操作C
  • ProtocolVersion2_1 extends ProtocolVersion2可以为A命令注册新操作,因为版本2.1中的行为与1.0中的行为不同。

基本上,这将查找和命令执行机制分离为trait / base-class,并为您提供了定义具体实现支持的命令的完全灵活性。

当然,所有这些都与Command pattern有关。

原则上,这不是你问题本身的答案,因为比赛没有延长。实际上,它实现了您想要做的事情,同时保持(摊销)不变的复杂性。