在用函数式编程语言编写代码时,是否有任何已知的原则、最佳实践和设计模式可以遵循?
7 回答
有折叠、展开、地图等。
我考虑使用它们的最佳实践,因为很容易推断它们的行为,并且它们经常传达函数的目的(例如,只需看看著名的 Haskell 程序员的进化,并将大一和大四进行对比,和教授)。
设计模式:让类型指导您的编码。
找出您要返回的类型。
知道某些类型构造函数带有某些语法,并利用它来使所需的类型更小。这里有两个例子:
如果你试图返回一个函数类型
T1 -> T2
,写起来总是安全的\ x -> ...
现在在正文中,您尝试生成一个类型的值
T2
,这是一个较小的类型,而且您获得了一个额外x
的类型值T1
,这可能会使您的工作更轻松。如果 lambda 被证明是不必要的,您可以随时 eta-reduce 稍后将其删除。
如果你试图产生一对类型
(T1, T2)
,你总是可以尝试产生一个x
类型T1
的值和一个类型的值y
,T2
然后形成对(x, y)
。同样,您已将问题简化为具有较小类型的问题。
一旦类型尽可能小,请查看范围内所有 let-bound 和 lambda-bound 变量的类型,并了解如何生成所需类型的值。通常,您希望对所有函数使用所有参数;如果你不这样做,请确保你能解释原因。
在许多情况下,尤其是在编写多态函数时,这种设计技术可以将复杂函数的构造减少到只有一两个选择。类型指导程序的构造,因此编写正确类型的函数的方法很少——而且通常只有一种没有明显错误的方法。
最佳实践:使用代数数据类型并利用模式匹配编译器的详尽检查。尤其是,
_
永远不要在顶层匹配通配符模式。设置编译器选项,以便模式匹配中缺少的大小写是错误,而不是警告。
不要遵循原则,遵循你的鼻子。保持功能简短。寻找降低代码复杂度的方法,这通常但不一定意味着最简洁的代码。了解如何使用内置的高阶函数。
编写函数后立即重构并减小函数的代码大小。这样可以节省时间,因为明天您将不会想到问题和解决方案。
设计模式:让编译器为您的函数推断类型,并确保这些类型与您期望的一样通用。如果类型是多态性的还是多态性的,找出原因。
例如,如果你在 Haskell 中编写一个排序函数,期望
Ord a => [a] -> [a]
如果你的类型是
Num a => [a] -> [a]
或者
[a] -> b
那么事情就大错特错了。
最佳实践:一旦您与编译器确认类型符合您的预期,就为每个顶级函数放置一个显式类型签名。(或者,如果您使用 ML 或 Caml,请编写显式接口。)设置编译器选项,以便缺少签名的值触发错误。
我会用可能有点模糊的原则来回答:努力使您的代码美观作为最重要的方面。引用大卫·格伦特的话:
美在计算中比技术中的其他任何地方都更重要,因为软件是如此复杂。美丽是对抗复杂性的终极防御。
和布赖恩·克尼汉:
控制复杂性是计算机编程的本质。
如果你追求这个目标,它将引导你编写易于阅读和理解的代码(这非常重要),将代码拆分成具有明确目的的小而紧凑的部分。它将引导您学习如何以特定语言以最佳方式表达您的想法。
(所有这些不仅适用于函数式语言,而且在它们中编写漂亮的代码要容易得多。)
John Hughes 的《为什么函数式编程很重要》很好地解释了为什么懒惰和高阶(一等)函数提供了很多功能较少的语言所缺少的东西,并补充了设计模式。
在 Haskell 的上下文中,我认为Real World Haskell这本书对习语、抽象和类型类等有一些很好的实用建议。Typeclassopedia也总是有用的。核心的、非常抽象的类型类可以被视为设计模式,除非它们由编译器/类型系统和语言的一部分强制执行(如果您学习如何使用它们)。
在 Lisp 的上下文中,Paul Graham 写了一本名为 On Lisp 的书(可在线获得),他在其中展示了函数式语言是创建自定义编程语言然后用它编写程序的理想选择。因此,嵌入式领域特定语言本身就是一种设计模式。