我最近开始使用 Haskell 学习函数式编程,并在 Haskell 官方 wiki 上看到了这篇文章:如何阅读 Haskell。
这篇文章声称,由于简洁和抽象,短变量名称,如x
,xs
和f
适合 Haskell 代码。从本质上讲,它声称函数式编程是如此独特的范式,以至于其他范式的命名约定不适用。
您对此有何看法?
我最近开始使用 Haskell 学习函数式编程,并在 Haskell 官方 wiki 上看到了这篇文章:如何阅读 Haskell。
这篇文章声称,由于简洁和抽象,短变量名称,如x
,xs
和f
适合 Haskell 代码。从本质上讲,它声称函数式编程是如此独特的范式,以至于其他范式的命名约定不适用。
您对此有何看法?
在函数式编程范式中,人们通常不仅自顶向下构造抽象,而且自底向上构造抽象。这意味着您基本上可以增强宿主语言。在这种情况下,我认为简洁的命名是适当的。Haskell 语言已经简洁且富有表现力,所以你应该习惯它。
但是,当尝试对某个领域进行建模时,我不认为简洁的名称是好的,即使函数体很小。领域知识应该体现在命名中。
只是我的观点。
我将从第 3 章中的Real World Haskell中获取两个代码片段。
在名为“更可控的方法”的部分中,作者提出了一个返回列表第二个元素的函数。他们的最终版本是这样的:
tidySecond :: [a] -> Maybe a
tidySecond (_:x:_) = Just x
tidySecond _ = Nothing
该函数足够通用,因为类型参数a
和我们对内置类型进行操作的事实,所以我们并不真正关心第二个元素实际上是什么。我相信x
在这种情况下就足够了。就像在一个小数学方程中一样。
另一方面,在名为“引入局部变量”的部分中,他们正在编写一个示例函数,试图对银行领域的一小部分进行建模:
lend amount balance = let reserve = 100
newBalance = balance - amount
in if balance < reserve
then Nothing
else Just newBalance
当然不建议在这里使用短变量名。实际上,我们确实关心这些金额代表什么。
我认为如果参数的语义在代码的上下文中是明确的,那么你可以使用短变量名。出于同样的原因,我经常在 C# lambda 中使用它们。但是,如果它不明确,您应该更明确地命名。
map :: (a->b) -> [a] -> [b]
map f [] = []
map f (x:xs) = f x : map f xs
对于没有接触过 Haskell 的人来说,这可能看起来是丑陋的、不可维护的代码。但是大多数 Haskell 程序员会马上明白这一点。所以它完成了工作。
var list = new int[] { 1, 2, 3, 4, 5 };
int countEven = list.Count(n => n % 2 == 0)
在这种情况下,短变量名似乎是合适的。
list.Aggregate(0, (total, value) => total += value);
但在这种情况下,命名变量似乎更合适,因为聚合在做什么并不是很明显。
基本上,我相信不要太担心惯例,除非绝对有必要防止人们搞砸。如果您在这件事上有任何选择,请使用在您正在工作的上下文(语言、团队、代码块)中有意义的内容,并且在数小时、数周或数年后阅读它的其他人可以理解。其他任何事情都只是浪费时间的强迫症。
我认为范围界定是#1 原因。在命令式语言中,动态变量,尤其是全局变量需要正确命名,因为它们在多个函数中使用。使用词法作用域,很清楚符号在编译时绑定到什么。
不变性也在一定程度上促成了这一点——在 C/C++/Java 等传统语言中,一个变量可以表示不同时间点的不同数据。因此,需要给它起一个名字,让程序员了解它的功能。
就个人而言,我觉得像一流函数这样的特性使符号名称变得非常多余。在传统语言中,与符号相关联更容易。根据它的用法,我们可以判断它是数据还是函数。
我现在正在研究 Haskell,但我不觉得它的命名约定有那么大的不同。当然,在 Java 中你很难找到像xs
. 但是很容易找到x
一些数学函数中的名称i
,j
例如计数器等。我认为这些名称在正确的上下文中是完全合适的。xs
在 Haskell 中,仅适用于列表的泛型函数。在 Haskell 中有很多,所以这个名字很普遍。Java 没有提供简单的方法来处理这样的通用抽象,这就是为什么列表(和列表本身)的名称通常更具体,例如lists
or users
。
我刚刚参加了许多有关 Haskell 的讲座,其中包含大量代码示例。只要代码处理 x,i 和 f 的命名就不会打扰我。然而,一旦我们开始处理繁重的列表操作等,我发现这三个字母左右的名称比我喜欢的可读性要低得多。
公平地说,命名的很大一部分遵循了一系列约定,所以我认为一旦你进入了行话,它会更容易一些。
幸运的是,没有什么能阻止我们使用有意义的名称,但我不同意语言本身以某种方式使三个字母标识符对大多数人有意义。
在罗马做到入乡随俗
(或者正如他们在我镇上所说的那样:“Donde fueres, haz lo que vieres”)
任何有助于可读性的东西都是一件好事——有意义的名字因此在任何语言中都是一件好事。
我在许多语言中使用短变量名,但它们保留用于在代码的整体含义中不重要或在上下文中含义明确的地方。
我会小心我接受有关 Haskell 名称的建议的程度
我的 Haskell 练习只是平庸的水平,因此,我敢尝试只回答你问题的第二个,更一般的部分:
“本质上,它声称函数式编程是如此独特的范式,以至于其他范式的命名约定不适用。 ”
我怀疑,答案是“是”,但我支持这种观点的动机仅限于一种单一功能语言的经验。不过,这可能很有趣,因为这是一种极其简约的语言,因此理论上非常“纯粹”,并且是许多实用函数式语言的基础。
我很好奇在像组合逻辑这样“极其”极简的函数式编程语言上编写实用程序是多么容易。
当然,函数式编程语言缺少可变变量,但组合逻辑“更进一步”,甚至缺少形式参数。它缺少任何语法糖,缺少任何预定义的数据类型,甚至是布尔值或数字。一切都必须由组合器模仿,并追溯到两个基本组合器的应用。
尽管有如此极端的极简主义,但仍有一些实用的方法可以以一种简洁而愉快的方式“编程”组合逻辑。我以模块化和可重用的方式在其中编写了一个quine ,即使在其上引导一个自我解释器也不会令人讨厌。
总而言之,在使用这种极其简约的函数式编程语言时,我感受到了以下特点:
需要发明很多辅助功能。在 Haskell 中,有很多语法糖(模式匹配、形式参数)。您可以用几行代码编写相当复杂的函数。但是在组合逻辑中,一个可以在 Haskell 中用单个函数表示的任务,必须用精心挑选的辅助函数代替。替换 Haskell 语法糖的负担由组合逻辑中巧妙选择的辅助函数承担。至于回答您最初的问题:值得为这些辅助功能军团发明有意义且吸引人的名称,因为它们可以在许多进一步的上下文中非常强大且可重用,有时以意想不到的方式。
此外,组合逻辑程序员不仅被迫找到一堆巧妙选择的辅助函数的醒目名称,而且更被迫(重新)发明全新的理论。例如,为了模仿列表,程序员被迫用它们的折叠函数来模仿它们,基本上,他必须(重新)发明变态、深代数和范畴论的概念。
我猜想,一些差异可以追溯到函数式语言具有强大的“粘合剂”这一事实。
在 Haskell 中,变量名比类型更能传达含义。纯函数式的优点是能够询问任何表达式的类型,而不管上下文如何。
我同意这里关于参数命名的很多观点,但是快速“在页面上查找”表明没有人提到默认编程(又名无点/无意义)。这是否更容易阅读可能是有争议的,所以这取决于您和您的团队,但绝对值得彻底考虑。
没有命名参数 = 没有参数命名约定。