函数式编程是命令式编程的一个子集吗?

9

函数式编程的主要特点之一是使用无副作用的函数。然而,这也可以在命令式语言中实现。递归和Lambda函数也是如此(例如C++0x)。因此,我想知道命令式编程语言是否是函数式编程语言的超集。


1
有趣的问题...在命令式编程中,我相信你有更多的方式可以"自杀" :-) - jldupont
就“无副作用函数”而言,我同意您的想法。可以编写并在两种范式中使用“无副作用函数”库,而有状态的命令式过程不能在函数式编程中使用。 - masterxilo
9个回答

21

我无法确定它们是否是彼此的子集。但是我可以告诉你(除了真正晦涩难懂的语言),它们都是图灵完备的,这意味着最终它们都同样强大,但不一定同样表达能力。


太有道理了...我希望我能给多于+1的赞。 - jldupont
Stephen Wolfram会说“具备通用计算能力”或类似的话。 - jldupont

9

一般来说不行;函数式编程是声明式编程的一个子集(包括逻辑编程语言,如Prolog)。许多命令式语言从函数式编程语言中借鉴了元素,但仅仅有lambda或引用透明函数并不能使命令式语言成为函数式的;函数式编程不仅仅是这些元素。


1
真的吗?我写过Prolog程序,还涉及到了SQL,也写过纯函数式程序。但是这两者对我来说似乎并不相似。 - David Thornley
1
它们在某种意义上是相似的,因为你告诉计算机要做什么(即表达计算逻辑),而不是如何做到这一点;这就是声明式编程的定义。 - mipadi
纯函数式语言确实存在,但是不纯的函数式语言则重叠而不是一个真正的子集。 - Zorf
@mipadi,你从哪里得出“函数式编程是声明式编程的子集”的结论的? - ricmed
@rickmed:从声明式编程的定义出发。 - mipadi

4
在不支持该编程范式的语言中实现某种编程范式是可能的。例如,虽然C语言并非为面向对象设计而开发,但仍可以编写面向对象代码。
函数式编程是一种完善的编程范式,最好通过Haskell、LISP等语言来学习。学习了它们之后,即使您不经常使用这些语言,也可以开始在日常使用的语言中应用这些原则。
有些人可能会搜索 C语言中的面向对象编程

1
除了最简单的“对象”之外,你所说的关于C语言的话毫无意义。没有多态和继承,面向对象编程还有什么意义? - user44242
2
如果你愿意的话,你可以在C语言中实现自己的虚函数表。 - jk.
你可以用C语言编写面向对象语言的解释器或编译器。这在大学里通常作为一项练习来完成。 - harms
是的,但公正起见,「在 C 中编写面向对象的代码」与「在 C 中编写面向对象语言的编译器」是不同的。 - mipadi
我曾经在一个支持面向对象的C语言框架上做过一些工作。这让我感觉到虽然可行,但并不是一个好主意。 - David Thornley
显示剩余2条评论

3
一种编程范式是一种做事情的方式,主要有两种编程范式:命令式和声明式。有些语言允许混合使用这两种范式,并不意味着其中一种包含在另一种中,而是表明这些语言是多范式的。为了更好地解释这一点,让我用您的比喻来继续说明:如果Lisp和OCaml(例如)被认为是函数式语言,并且它们都允许命令式风格...那么命令式是否应该被认为是函数式的子集?

Lisp(特别是Common Lisp)通常被称为多范式语言。您可以相对轻松地编写命令式和函数式代码。 - Vatine
1
Lisp被称为多范式语言,就像C++一样。纯粹主义者会说它们是多范式的(他们是正确的),但在常见用法中,它们通常分别被称为函数式和命令式/面向对象。无论如何,我的观点是,如果你能够同时使用两种范式,那么这种语言就是多范式的,因此op的问题从一开始就被错误陈述了。 - fortran

2

从一种角度来看(并不是说这是正确的方式,因为我并不是语言设计师或理论家),如果语言本质上转换成其他东西,那么“其他东西”必须是源代码的超集。因此,字节码必须是Java的超集。.NET IL是C#和F#的超集。 C#中的函数式结构(即LINQ)因此是IL的命令结构的子集。

由于机器语言是命令式的,因此您可以认为所有语言都是命令式的,因为它们只是对人类有用的抽象,然后被编译器煮掉成过程性的、命令式的机器代码。


+1 很好的评论!我想点赞,但它不起作用(“投票太旧无法更改”)。抱歉:( - Dimitri C.

2

大多数命令式语言没有将函数作为一级类型,而大多数函数式语言则有(通过boost::function,C++也可以实现)。

所谓的一级类型是指一个值/变量可以是任何类型,比如int、bool、从int到bool的函数。通常还包括闭包或绑定值,其中你有相同的函数,但某些参数已经填充。

在我看来,这两个方面是函数式编程的主要内容。


2
我认为区分“范式”和“语言”可能会有所帮助。
对我而言,“范式”代表着“思考方式”(例如函数、对象、递归等概念和抽象),而“语言”提供了“执行方式”(语法、变量、评估)。
所有真正的编程语言在本质上都是等效的,因为它们都是图灵完备的,并且理论上能够计算任何图灵可计算函数,以及模拟或被通用图灵机模拟。
有趣的是,在某些语言或范式下完成某些任务是多么困难,以及工具与任务的适当性。即使康威生命游戏也是图灵完备的,但这并不会让我想用它来编程。
许多语言支持多种范式。C++被设计为C的面向对象扩展,但在其中纯过程化代码也是可能的。
一些语言随着时间的推移从其他语言或范式中借用/获取功能(只需看看Java的演变即可)。
像Common Lisp这样的语言是令人印象深刻的多范式语言。在Lisp中,可以编写函数式、面向对象或过程化的代码。可以说,方面导向已经成为Common Lisp对象系统的一部分,因此“没有什么特别的”。在Lisp中,可以轻松地扩展语言本身以执行所需的任何操作,因此有时被称为“可编程编程语言”。 (这里我要指出,Lisp描述了一系列语言,其中Common Lisp只是其中一种方言)。
我认为重要的不是哪个术语(声明式、命令式、函数式或过程化)是哪个子集。更重要的是要理解你正在使用的语言工具以及它们与其他工具的区别。更重要的是要理解范式所代表的不同思考方式,因为这些是你的思维工具。像生活中的大多数其他事情一样,你越理解,就会越有效率。

“无论哪个术语是另一个的子集,都不重要。”我之所以问这个问题,部分原因是因为我喜欢函数式编程的一些概念,但我不喜欢它的限制,比如处理I/O的困难方式和大量使用递归的问题。 - Dimitri C.
递归从何时开始成为一种限制?循环和递归是同一枚硬币的两面,但其中一个通常比另一个更合适。函数式编程是一种范式,具体的语言(其语法和库)决定了像I/O或循环这样的操作的“难度”。您使用“臭名昭著”一词表明您还没有自己好好看看。请自行查看 - 我发现在Java 2中理解I/O相当“困难”,但我只使用Javadocs来弄清楚它。选择一种对您来说“友好”的语言并开始阅读相关资料。 - Galghamon

1

类似于模式映射

f:: [int] -> int
f [] = 0
f (x:xs) = 1 + f(xs)

某些东西例如在命令式语言中不可用。 还有像柯里化函数这样的结构:

add2 :: int -> int
add2 = (2 +)

在大多数命令式语言中不可用


可以使用模板在C++中进行柯里化:https://dev59.com/BXVC5IYBdhLWcg3w51hv - Dimitri C.

0

是的,函数式编程是命令式编程的一个子集,但是...

是的,因为在函数式编程中没有任何你不能在命令式编程中做到的事情(语法差异除外)。你可以在命令式语言中“做”函数式编程。

但是... 你不能做的事情是函数式编程的关键特性。 通过限制你所能做的事情,

  1. 你使某些错误变得不可能,
  2. 你可以启用功能(例如程序分析、更简单的并发、更简单的测试等)。

函数式编程的其他好处更加主观。 你经常听到这些争论。

  1. 状态允许副作用。副作用是不好的。函数式编程没有状态。你不能没有状态就有副作用。

这是一个可疑的好处。 首先,只有无意的副作用是不好的。适当的编程实践,例如将对状态的修改限制为特权代码,可以缓解副作用问题。 其次,函数式编程只有内部状态。如果程序具有IO(访问文件、网络、硬件),则具有外部状态,因此具有副作用的潜力。

  1. 函数式程序更容易调试。

在某些方面是的,比如知道异常的显式路径。 但是有状态可供检查是函数式编程没有的调试优势。

  1. 函数式程序更容易理解。

只有当您精通函数式编程而不精通命令式编程时,这才是真的。 就我个人而言,作为一名更精通命令式编程的人,我认为这个论点是错误的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接