我最近编写了一些代码,如下面的块,它让我想到如果我对函数式编程抽象更了解,设计可以改进。
sealed trait Foo
case object A extends Foo
case object B extends Foo
case object C extends Foo
.
.
.
object Foo {
private def someFunctionSemanticallyRelatedToA() = { // do stuff }
private def someFunctionSemanticallyRelatedToB() = { // do stuff }
private def someFunctionSemanticallyRelatedToC() = { // do stuff }
.
.
.
def somePublicFunction(x : Foo) = x match {
case A => someFunctionSemanticallyRelatedToA()
case B => someFunctionSemanticallyRelatedToB()
case C => someFunctionSemanticallyRelatedToC()
.
.
.
}
}
我的问题是:
答案 0 :(得分:5)
你刚刚遇到expression problem。在您的代码示例中,问题是每次从Foo
代数数据类型添加或删除案例时,您都需要修改每个匹配项(如somePublicFunction
)中的值Foo
。在Nimrand的答案中,问题出现在相反的一端:您可以轻松地从Foo
添加或删除案例,但每次要添加或删除行为(一种方法) ),您需要修改Foo
的每个子类。
有许多解决表达式问题的建议,但有一个有趣的功能方法是Oleg Kiselyov的Typed Tagless Final Interpreters,它用代码数据类型代替每个用于返回一些摘要的函数被认为等同于该情况的价值。使用泛型(即类型参数),这些函数都可以具有兼容的类型,并且无论何时实现它们都可以相互协作。例如,我已经实现了使用TTFI构建和评估算术表达式树的示例:https://github.com/yawaramin/scala-ttfi
答案 1 :(得分:1)
你的解释有点过于抽象,无法给你一个自信的答案。但是,如果Foo的子类列表将来可能会增长/改变,我倾向于将它作为Foo的抽象方法,然后为子类中的每个案例实现逻辑。然后你只需调用Foo.myAbstractMethod()并且多态性可以整齐地处理所有事情。
这使得每个对象的代码都与对象本身保持一致,从而使事情更加整齐有序。它还意味着您可以添加新的Foo子类,而无需跳转到代码中的多个位置来扩充代码中其他位置的现有匹配语句。
当子类集相对较小且固定时,案例类和模式匹配最有效。例如,Option [T]只有两个子类,Some [T]和None。这将永远不会改变,因为改变将从根本上改变Option [T]所代表的含义。因此,它是模式匹配的良好候选者。