【问题标题】:How does Pattern Matching in Scala overcome duplication that switch case causes?Scala 中的模式匹配如何克服 switch case 导致的重复?
【发布时间】:2012-07-24 10:25:11
【问题描述】:

注意:我问这个问题是出于好奇,而不是质疑语言功能的重要性。

看起来是向命令式编程世界的人们介绍的一项很棒的功能。 我是 Scala 的新手,但仍在尝试找出所有内容的位置,使其大量构造适合并可以利用。

模式匹配绝对可以比开关盒做得好 100 倍。 但是,自从 OOP 出现以来,我们仍然倾向于使用多态性。

简而言之,我觉得难以理解的是,如果 switch case 鼓励重复,我们最好将 case 相关的代码写入相应的类,那么 Scala 的模式匹配如何克服这个问题?

我们仍然可以为各种情况使用类或泛型类,并再次利用多态性来满足我们的需求。

【问题讨论】:

  • 简答:algebraic data types。模式匹配是使用 ADT 的一种非常简单和优雅的方式。如果您的代码更面向对象,则多态可能更适合。
  • “它的大量构造集”——不要让 Martin Odersky 读到它。他为 Scala 的结构集比其他语言小得多这一事实感到自豪。他更喜欢一种具有少量强大、可组合、正交结构的语言。通过统一例如将功能和对象整合到一个结构中,或者将模块、组件和对象统一到一个结构中。或者使用继承来实现代数数据类型。
  • @JörgWMittag:他确实做到了简单。但同时也为经验丰富的 Java 开发人员带来了很多新事物。所有这些都是非常强大的功能,但前提是我们要花时间一一理解它们。纯粹的 OOness 融合了丰富的功能特性真的很棒。但与此同时,演员、模式匹配、FP 本身是许多新事物,Java 开发人员很容易感到困惑。我在想是用 Java 的方式做 Scala 还是尽可能地跳到演员和模式匹配中去。
  • 我不明白你的问题。你能解释一下你的意思a)“重复”,b)“编写类型相关代码”吗?
  • @0__:我有点困惑,什么时候进行模式匹配,什么时候进行多态,因为我根本不打算用它来代替 switch,因为我根本不使用它。所以我想知道在 Scala 中编写 OO 代码时模式匹配的一般用例是什么,而不是代数数据类型,这也是大多数 scala 书籍中的代数数据类型。敏捷钢的回答为我清除了这一点。

标签: oop scala design-patterns


【解决方案1】:

这是对象和数据结构的区别。

如果您正在处理对象,请使用子类型多态性 - 添加新类型不需要重新编译、重新测试或重新部署现有类型,而添加新算法(接口上的方法,位于层次结构)确实如此。

如果您正在处理数据结构,请使用模式匹配 - 添加新算法不需要重新编译、重新测试或重新部署现有算法,而添加新类型则需要。

阅读更多相关信息here

【讨论】:

  • 请参阅论文“Independently Extensible Solutions to the Expression Problem”(zenger.org/papers/fool05.pdf),以非常深入地比较面向对象和众所周知问题的函数分解。作者还确定了可扩展性的两个维度,即添加行为和添加子类型,他们说明经典的 OO(行为)和经典的 FP(子类型)方法在 w.r.t. 上运行良好。维度之一,但不是很好 w.r.t.给对方。他们得出的结论是,Scala 允许您将这两种范式结合起来,从而克服了这个问题。
【解决方案2】:

模式匹配是一个很棒的功能,因为它易于使用。

它比广泛使用的面向对象语言中的大多数设计模式更好地解决了“如何为对象系统带来功能”的问题。例如,Visitor pattern 将算法与其对象结构分开。这个想法很棒,因为它允许我们改变对象的行为而无需触及它们的类。但另一方面,这种模式在符号的过于复杂和冗长方面失败了。通过模式匹配,这个问题可以轻松解决:

def calc(e: Expression): Double = e match {
  case Num(n) => n
  case Add(a, b) => calc(a)+calc(b)
  case Sub(a, b) => calc(a)-calc(b)
  ...
}

这将 AST 的计算与其定义分开,并且比多态访问者模式更易于阅读。

因为模式匹配非常简单,所以我们可以在任何地方使用它——您会在大多数 OO 语言中从未想过的地方找到它。一个很好的例子是 Actors,它使用代数数据类型 (ADT) 在彼此之间进行通信:

sealed trait Message
case class Hello(name: String) extends Message
case class Bye(name: String) extends Message
case class Question(q: Symbol) extends Message

class MySelf extends Actor {
  def receive {
    case Hello(name) => println("Hello "+name)
    case Bye(name) => println("Buy "+name)
    case Question('AreYouOk) => sender answer "I'm ok"
    ...
  }
}

希望您能通过访问者模式实现这一点,玩得开心!

【讨论】:

  • 我看不出访问者模式和参与者有什么关系。前者是一种强制分离关注点(这里:结构和行为)的方法,后者是一种并发模型。
  • @mhs:域无关紧要。在这些情况下,模式匹配有助于解决比多态类型更优雅的问题。这就是我试图展示的。
  • 我可以看到 ADT 和模式匹配为您提供了一种分解消息的好方法。但是,您的最后一句话表明,如果 ADT 不可用,访问者模式将是这里的自然选择,这就是我不同意的地方。但我可能只是误解了这一点。
  • @sschaef: 演员的recive块是否与模式匹配中的匹配块相同?
  • @mhs:我最后一句话的意思是:“您可以使用访问者模式来实现相同的目标,但需要更多样板”
【解决方案3】:

我看到模式匹配完成 OOP 并允许更多模块化编程的几点。

  1. 当您有一个大项目时,您希望避免在您的域类中放置“过多的行为”。您可以将行为移到外部,并且通常有一个方法可以接收层次结构顶部的类并与子类匹配。

  2. 当您使用特定库并且想要添加行为但无法修改源时。您也可以为此使用隐式转换,但在简单的情况下,模式匹配更快更容易。

为了回答你的问题,我可能会说你低估了模式匹配可以带来的代码重用:当你创建一个匹配块时,它会创建一个 PartialFunction。如果您需要重用您的模式匹配块,您可以通过 orElse 方法使用 PartialFunction 链接。这在设计特定对象的分层处理程序集时也带来了好处,因为匹配是按顺序执行的。

【讨论】:

  • "当您有一个大项目时,您希望避免在您的域类中放置“太多行为”。"断然错误。 Fowler、Martin、Evans 等人都强烈主张状态+行为是 OOP 的基础。多态性和委托是耦合状态和行为的完全有效的抽象技术。对于较大的应用程序,通常(错误地)导致从域对象中剥离逻辑的问题是试图建立一个全局统一模型。限界上下文模式很好地解决了这个问题。
  • 在 OOP 世界中,我同意你所说的,但如果我们展望一个更大的世界,包括具有功能特性的语言,我不同意。您可能还想阅读此ropas.snu.ac.kr/~bruno/papers/TypeClasses.pdf
  • 虽然我不同意软件工程中功能特性的价值(类型类是有用构造的一个很好的例子),但事实仍然是,大型项目在尝试中往往会走错方向维护全球统一的模式。将模型及其行为划分为可管理的部分是第一个要解决的问题。然后,就传统 OOP 技术与“更纯”的 FP 方法做出决定。当域对象的命名空间已经太大时尝试执行后者是灾难的邀请。
【解决方案4】:

继承和案例构造都是实现多态性的有效方法。它们在稍微不同的情况下都很好。与基于继承的多态不同,模式匹配是不可扩展的,但通常你不需要它。函数式编程中的许多结构,例如 Option、Either 或 :: 可以更简洁地与 oop 多态性和 if 语句的模式匹配一​​起使用。一般来说,任何问题都可以用任何一种多态性来解决。这只是优雅的问题。

【讨论】:

    【解决方案5】:

    如果你滥用它们,它们实际上并没有那个确切的问题。

    正如继承的多态性会导致您的类吸引不属于该类的各种方法时遇到问题。

    虽然 Java 对继承有一些合理的强大支持,但 switch 语句只是一个笑话。

    在 Scala 中,您对继承和惊人的模式匹配有更强大的支持。

    为你的钉子挑选合适的锤子是你的工作。

    【讨论】:

      猜你喜欢
      • 2011-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多