我已经阅读了维基百科上关于过程式编程和函数式编程的文章,但我还是有点困惑。有人能把它归结为核心吗?


当前回答

康拉德说:

因此,纯函数式程序总是为输入产生相同的值, 评价的顺序也不明确;这意味着不确定的值,比如 用户输入或随机值很难用纯函数式语言建模。

在一个纯函数式程序中求值的顺序可能很难(或者)解释(尤其是懒惰的人),甚至不重要,但我认为说它没有被很好地定义,听起来就像你根本无法判断你的程序是否会工作!

Perhaps a better explanation would be that control flow in functional programs is based on when the value of a function's arguments are needed. The Good Thing about this that in well written programs, state becomes explicit: each function lists its inputs as parameters instead of arbitrarily munging global state. So on some level, it is easier to reason about order of evaluation with respect to one function at a time. Each function can ignore the rest of the universe and focus on what it needs to do. When combined, functions are guaranteed to work the same[1] as they would in isolation.

... 像用户输入或随机值这样的不确定值很难纯粹地建模 函数式语言。

The solution to the input problem in purely functional programs is to embed an imperative language as a DSL using a sufficiently powerful abstraction. In imperative (or non-pure functional) languages this is not needed because you can "cheat" and pass state implicitly and order of evaluation is explicit (whether you like it or not). Because of this "cheating" and forced evaluation of all parameters to every function, in imperative languages 1) you lose the ability to create your own control flow mechanisms (without macros), 2) code isn't inherently thread safe and/or parallelizable by default, 3) and implementing something like undo (time travel) takes careful work (imperative programmer must store a recipe for getting the old value(s) back!), whereas pure functional programming buys you all these things—and a few more I may have forgotten—"for free".

我希望这听起来不像狂热,我只是想补充一些观点。命令式编程,特别是像c# 3.0这样的强大语言中的混合范式编程,仍然是完成工作的完全有效的方法,并且没有银弹。

[1]…除了内存使用方面(参考Haskell中的foldl和foldl')。

其他回答

在计算机科学中,函数式编程是一种编程范式,它将计算视为数学函数的求值,并避免状态和可变数据。它强调函数的应用,与强调状态变化的过程式编程风格相反。

Funtional编程

num = 1 
def function_to_add_one(num):
    num += 1
    return num


function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)

#Final Output: 2

过程式编程

num = 1 
def procedure_to_add_one():
    global num
    num += 1
    return num


procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()

#Final Output: 6

Function_to_add_one是一个函数

Procedure_to_add_one是一个过程

即使你运行这个函数5次,每次它都会返回2

如果你运行这个过程五次,在第五次运行结束时,它会给你6。

免责声明:显然,这是对现实的一种超简化的看法。这个答案只是让我们了解了“函数”而不是“过程”。仅此而已。一旦你尝到了这种肤浅而深刻的直觉,开始探索这两种范式,你就会开始清楚地看到它们的区别。

对我的学生有帮助,希望对你们也有帮助。

函数式编程与不使用全局变量的过程式编程相同。

我从来没有在其他地方看到过这样的定义,但我认为这很好地总结了这里给出的差异:

函数式编程主要关注表达式

过程式编程主要关注语句

表达式有值。函数式程序是一个表达式,其值是由计算机执行的一系列指令。

语句没有值,而是修改一些概念机器的状态。

在纯函数式语言中,没有语句,也就是说没有办法操纵状态(它们可能仍然有一个名为“语句”的语法结构,但除非它操纵状态,否则我不会在这种意义上称其为语句)。在纯程序语言中,没有表达式,一切都是操纵机器状态的指令。

Haskell是纯函数式语言的一个例子,因为没有办法操纵状态。机器代码是纯过程语言的一个例子,因为程序中的所有内容都是操作机器寄存器和内存状态的语句。

令人困惑的部分是,绝大多数编程语言同时包含表达式和语句,允许您混合使用范式。语言可以根据它们鼓励使用语句和表达式的程度被分类为更函数化或更过程化。

For example, C would be more functional than COBOL because a function call is an expression, whereas calling a sub program in COBOL is a statement (that manipulates the state of shared variables and doesn't return a value). Python would be more functional than C because it allows you to express conditional logic as an expression using short circuit evaluation (test && path1 || path2 as opposed to if statements). Scheme would be more functional than Python because everything in scheme is an expression.

你仍然可以在一种鼓励过程范式的语言中以函数式风格编写,反之亦然。只是在语言不鼓励的范式下写作更困难和/或更尴尬。

这里没有一个答案显示了惯用的函数式编程。递归阶乘的答案很适合在FP中表示递归,但大多数代码不是递归的,所以我不认为这个答案是完全具有代表性的。

假设你有一个字符串数组,每个字符串表示一个整数,比如“5”或“-200”。您希望根据内部测试用例检查这个输入字符串数组(使用整数比较)。两种解决方案如下所示

程序上的

arr_equal(a : [Int], b : [Str]) -> Bool {
    if(a.len != b.len) {
        return false;
    }

    bool ret = true;
    for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
        int a_int = a[i];
        int b_int = parseInt(b[i]);
        ret &= a_int == b_int;  
    }
    return ret;
}

功能

eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization

arr_equal(a : [Int], b : [Str]) -> Bool =
    zip(a, b.map(toInt)) # Combines into [Int, Int]
   .map(eq)
   .reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value

虽然纯函数式语言通常是研究语言(因为现实世界喜欢免费的副作用),但现实世界的过程式语言在适当的时候会使用更简单的函数式语法。

这通常是用Lodash这样的外部库实现的,或者是用Rust这样的新语言内置的。函数式编程的繁重工作是通过map、filter、reduce、currying、partial等函数/概念完成的,最后三个你可以查阅以进一步理解。

齿顶高

In order to be used in the wild, the compiler will normally have to work out how to convert the functional version into the procedural version internally, as function call overhead is too high. Recursive cases such as the factorial shown will use tricks such as tail call to remove O(n) memory usage. The fact that there are no side effects allows functional compilers to implement the && ret optimization even when the .reduce is done last. Using Lodash in JS, obviously does not allow for any optimization, so it is a hit to performance (Which isn't usually a concern with web development). Languages like Rust will optimize internally (And have functions such as try_fold to assist && ret optimization).