Python:列表推导和函数式编程

7
在我学习Python的书中,当我阅读列表推导式时,作者有一小段说明:

Python的列表推导式是该语言支持函数式编程概念的一个例子。

我去维基百科上了解函数式编程,但我很难想象它们之间的联系,因为我没有看到任何关于列表推导式和这个概念的解释。
请给我一个清晰的解释(如果可以的话,请还给我一些Java或C#中函数式编程的示例:D)。

Java和C#不是函数式编程语言。 - Denis
1
@Denis,不好意思,你错了。有很多例子可以证明LINQ是基于函数式编程的。而且还有一本书叫做“Functional programming in .NET”。至于Java,我不清楚。 - hqt
5
C#是面向对象编程语言,微软开发了函数式编程语言F#,你混淆了编程范式和语法糖。 - Denis
1
在面向对象编程范式中,您可以通过继承、多态和封装来描述现实世界中的对象,例如汽车->卡车->沃尔沃卡车。但是在函数式编程范式中,您可以使用lambda、monad等来将同一世界描述为有限自动机集合。很抱歉,我的英语不太好,无法进行这种性质的讨论。 - Denis
1
@alan 我的问题是:我想解释为什么。我认为,如果我有更多的例子,我会更清楚地理解 :) - hqt
显示剩余3条评论
5个回答

7
如果你的问题是“请给我一些示例,展示FP在Python中如何工作”,那么: 什么是纯函数式编程(在Python中)? 这是一种编程范式,避免使用状态和可变数据,而是依赖于函数返回值。这意味着用Python编写的纯函数式程序不会有像变量、状态等东西。 不太纯的FP 您可以将FP和命令式范式结合起来,并取得良好的结果(参见 here)。链接的代码片段是一个数学测验程序,我为之前参加的一个Python课程编写的。您可以随意使用这些代码。 Java/C# 中的FP 我个人没有C#方面的经验,所以需要其他人发布C#示例,但是您可以在Java中使用FP,但不能使用纯FP。例如:
int fib (int x) { 
    if (x < 2) return x;
    return fib (x-1) + fib(x-2);
}

上述方法完全是函数式编程(FP),但在使用Java时无法在纯FP上下文中使用。这需要将其放置在Java类C中,并且只能在实例化该类型的对象之后调用。这最后一部分使Java类C无法符合FP,但该方法仍将是FP。
编辑:实际上,您可以在Java中拥有静态方法,这些方法可以在没有任何实例化的情况下使用。因此,如果将签名更改为`static int fib(int x)`,则在以FP方式调用方法及其方法调用时,该方法仍可能是FP的。
关于您的评论:
递归可能是FP,但不一定要这样(请参见下文):
def f(first, rest):
    print first
    first = rest[0]; rest = rest[1:]
    f(first, rest)

没有递归也可以使用FP:

 def sum (a,b):
     return a+b

 def square(c):
     return c*c

 def square_of_sum (x,y):
     return square(sum(x,y))

1
哦,你的代码是递归的,我们称之为FP?如果是这样,这个例子对我来说并不太有说服力,因为它比其他方法慢得多。 - hqt

5
Python的map()、reduce()和filter()会对一个序列进行处理,应用另一个你选择的函数,然后将不同的序列返回给你,保留原始序列的完整性。
你可以说这是函数式的,因为它不会触及原始序列,不会在内部改变其状态,也不会产生副作用。(尽管你自己提供的函数可能会执行一些上述操作,例如产生副作用) 函数式编程是一种不同的编程和应用结构化方式,旨在减少由副作用(直接更改另一个静态位置或进程中的某个值)引起的错误,并减少或消除同步访问共享数据的需要。一些语言强制你这样做,比如Erlang,而其他语言则更多地让你选择哪条路线最适合你(过程式或函数式),并倾向于编程光谱的函数式一侧,例如Scala

副作用。我真的不太理解这个术语。你能给我举个例子吗?(它意味着我不知道在面向对象编程中哪些问题会产生副作用,而在函数式编程中则不会产生副作用。) - hqt
4
副作用基本上是指任何影响“外部世界”的事情(外部意味着超出你的函数范围之外),即使是简单的文本打印也算是副作用。 - Harald Brinkhof
我并没有看到side-effect有什么不同。如果我理解错了,请见谅,但是关于affect out side world,我认为它与编程技术有关。例如在C语言中:public void A(int a);不会对任何东西产生影响,但是public void A(int*a);将会影响变量a。 - hqt
1
是的,这正是关键所在。假设*a(可以是任何东西)在另一个进程/线程使用它时被并发进程或线程更改。由于这个原因,你例行程序的结果可能与进入时不同。在这种情况下防止这种情况的唯一方法是通过互斥锁锁定数据访问。纯函数式编程风格的实际优势只有在程序以并行方式执行其例行程序时才会真正显示出来,因为不变性和消息传递保证您只处理提供的数据。 - Harald Brinkhof
1
它基本上确保double(a) [a=4]返回8而不是16,因为另一个进程更改了在到达return a * 2之前(a)所代表的任何内容。 (这是可能的,因为CPU可以在进入例程但在进行计算之前抢占您的线程,如果您没有明确禁止它这样做)它保证了您例程的功能不变性。 - Harald Brinkhof
1
@hqt 一个副作用的简单例子是,如果你的函数修改了全局变量。 - Levon

4
我相信Python的列表推导式直接借鉴了Haskell(一种非常“纯”的函数式语言)。
Haskell:
[ x | x <- [1..10] ]

Python:

[ x for x in range(1,11) ]

正如其他人所提到的,Python确实允许使用函数式概念,例如map()reduce()lambda

虽然这些都是函数式思想,但由于Python不友好地支持递归,它们很少能以纯函数式方式使用。

如果您想了解“功能性”语言,请查看'Haskell'、'Scala'、'Clojure'、'Erlang'、'F#'等,它们都更或多或少地具有函数式(尽管有人可能认为这不是这种情况)。

如果您真的想了解函数式编程的内容,请看这里。学习Haskell进行伟大的事业,这很容易阅读,有漂亮的图片,会开启您的眼界。

编辑 -

Haskell阶乘函数的示例(所有函数都做同样的事情):

fact1 0 = 1
fact1 n = n * fact1 (n - 1)


fact2 n | n == 0 = 1
        | otherwise = n * fact2 (n - 1)


fact3 n = case n of
            0 -> 1
            _ -> n * fact3 (n - 1)

顺便看一下这个问题,因为它也很相关。


1
嗯,我不会说Python的列表推导式来自Haskell,但很可能来自集合论…… Haskell和Python只是实现这种语法糖的长列表中的一部分 - cedbeu

1
我相信其他人能够比我更好地解释,但函数式编程主要与程序流程的思考方式有关,以及您是否可以将函数作为对象传递以进行计算。例如,在JavaScript中,当提供要在事件触发时执行的函数时,这就是传递函数的一种方式,在这个意义上它几乎像函数式编程。
这就是列表推导式类似于函数式编程的意义,因为您正在提供有关如何计算每个元素的指令,而不是更加过程化的方法,即循环并自己进行计算,而不是将其作为函数交给别人处理。Python并不是我认为真正的函数式编程语言,例如LISP、ML或Haskell(Erlang呢?我记不清了),但它可以做一些类似的事情(请查看Python中的lambda表达式)。
Java和C/C++也不是真正的函数式编程语言,但您可以使用函数指针作为参数来模拟它。对C#不是很熟悉...
事件驱动语言倾向于更多地利用这种函数传递的想法,因为它们需要一些方法来传递未知代码以在以后执行。

0

这很简单。Map和Reduce最初来自Lisp语言和函数式编程。

而Python则有filter、map和reduce函数。

参考资料:http://www.joelonsoftware.com/items/2006/08/01.html

 http://docs.python.org/tutorial/datastructures.html

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