|
|
1
22
AS 大流士培根 注意到,这本质上是表达问题,一个长期存在的问题,没有普遍接受的解决方案。然而,缺乏两全其美的方法并不能阻止我们有时想走一条或另一条路。现在,你要求 “功能语言的设计模式” 我们来试试看。下面的示例是用haskell编写的,但不一定是haskell(或任何其他语言)的惯用语言。 首先,快速回顾一下“表达问题”。考虑以下代数数据类型:
这表示简单的数学表达式,只包含文字值和加法。使用这里的函数,我们可以获取表达式并对其进行计算,或者将其显示为
容易的!我们可以不费吹灰之力地整天写函数!代数数据类型太棒了! 事实上,他们太棒了,我们想让我们的表达类型更多,呃,表达。让我们把它扩展到支持乘法,我们就…嗯…哦,天哪,那会很尴尬,是吗?我们必须修改刚才编写的每个函数。绝望! 实际上,扩展表达式本身可能比添加使用它们的函数更有趣。所以,假设我们愿意在另一个方向上进行权衡。我们该怎么做? 好吧,半途而废毫无意义。让我们 把所有的事情都结束,然后把整个程序颠倒过来。 那是什么意思?好吧,这是函数式编程,还有什么比高阶函数更有用呢?我们要做的是 用表示表达式操作的数据类型替换表示表达式值的数据类型 . 我们不需要选择一个构造函数,而是需要一个所有可能操作的记录,如下所示:
那么,我们如何创建一个没有数据类型的表达式呢?我们的函数现在是数据,所以我想我们的数据应该是函数。我们将使用常规函数生成“构造函数”,返回操作记录:
我们现在能更容易地加乘法吗?当然可以!
哦,但是等等——我们忘了加一个
无论如何,使用这两种不同的样式是什么样子的?
几乎是一样的,当你不扩展它们的时候。
作为一个有趣的旁注,考虑到在“actions”样式中,表达式中的实际值是
完全隐藏
——
这种编程风格——将简单的数据替换为一束“操作”,同时将实际的实现细节隐藏在一个黑盒中,使用类似构造函数的函数来构建新的数据位,能够用相同的“操作”集交换非常不同的“值”,等等——非常有趣。可能有个名字,但我不太记得… |
|
|
2
6
我听过这个抱怨不止几次,总是让我困惑。提问者写道:
但这大体上是一个特性,而不是一个bug!例如,当您更改变量中的可能性时,通过模式匹配访问该变量的代码将被迫考虑出现新可能性的事实。这是很有用的,因为实际上,您确实需要考虑代码是否需要更改以响应它所处理的类型中的语义更改。 我会反驳这种说法,即需要“大量的代码更改”。对于编写良好的代码,类型系统通常能够突出需要考虑的代码,而不是更多。 也许这里的问题是,如果没有更具体的例子,很难回答这个问题。考虑在haskell或ml中提供一段不确定如何干净地进化的代码。我想你会得到更精确和有用的答案。 |
|
|
3
4
这种权衡在编程语言理论文献中被称为 expression problem :
有人提出了解决办法,但我没有研究过。(很多讨论 at Lambda The Ultimate ) |
|
|
4
3
在haskell中,至少我会创建一个抽象数据类型。即创建不导出构造函数的类型。该类型的用户缺乏对该类型进行模式匹配的能力,您必须提供使用该类型的函数。作为回报,您可以得到一个更容易修改的类型,而无需更改该类型的用户编写的代码。 |
|
|
5
0
如果新的数据意味着没有新的行为,比如在一个应用程序中,我们被要求将“生日”字段添加到“个人”资源中,然后我们所要做的就是将其添加到作为个人资源一部分的字段列表中,那么在功能和OOP世界中都很容易解决。不要把“生日”作为代码的一部分,它只是数据的一部分。 让我解释一下:如果生日意味着一种不同的应用行为,例如,如果该人未成年,我们会做一些不同的事情,那么在OOP中,我们会向Person类添加一个生日字段,在FP中,我们会向Person数据结构添加一个类似的生日字段。 如果“生日”没有附加的行为,那么代码中不应该有名为“生日”的字段。字典(地图)等数据结构将保存各种字段。添加一个新的将不需要程序更改,无论您是OOP还是FP。通过附加验证regexp或使用类似的验证小语言来表示 在数据中 验证行为应该是什么。 |