功能编程和非功能编程


71

在大学的第二年,我们被“教”了Haskell,我对此几乎一无所知,甚至对函数式编程一无所知。

什么是函数式编程,为什么和/或在什么地方我想用它代替非函数式编程?我是否认为C是一种非函数式编程语言是正确的?

Answers:


90

功能语言的一个主要功能是一流功能的概念。这个想法是,您可以将函数作为参数传递给其他函数,然后将它们作为值返回。

函数式编程涉及编写不会更改状态的代码。这样做的主要原因是,对函数的连续调用将产生相同的结果。您可以使用任何支持一流功能的语言编写功能代码,但是有些语言(例如Haskell)不允许您更改状态。实际上,您根本不应该产生任何副作用(例如打印文本)-听起来可能完全没有用。

Haskell则对IO采用了另一种方法:monads。这些对象包含要由解释程序的顶层执行的所需IO操作。在任何其他级别上,它们只是系统中的对象。

函数式编程提供什么优势?由于每个组件都是完全隔离的,因此函数式编程可以减少潜在的错误编码。同样,使用递归和一流的功能还可以提供简单的正确性证明,这些证明通常反映了代码的结构。


7
“这样做的主要原因是,对函数的连续调用将产生相同的结果”Why do we want successive calls to a function to yield the same result. What is the problem with the way things are in C? I never understood this.
Lazer 2010年

16
@Lazer这是不受欢迎的一些原因。首先,如果函数返回相同的结果,则可以缓存该结果,如果再次调用该函数,则将其返回。其次,如果函数没有返回相同的结果,则意味着该函数依赖于程序的某些外部状态,因此,该函数不容易被预先计算或并行化。
凯尔·克罗宁

9
在分析和调试代码时,这也是一个巨大的好处。副作用基本上只是对程序员隐藏的函数的隐式输入。
Magnus Kronqvist,2011年

@MagnusKronqvist如果它们是可变的,则它们与隐式输入不同,因为它们可以被写入。输入参数通常无法写入(除非它们是引用类型)。区别的重要性在于,访问可变的外部状态使函数势在必行,而不是声明性的
谢尔比·摩尔三世

“您可以用任何支持一流功能的语言编写功能代码”-我要补充一点:1)在该语言中具有类型类支持,可以避免很多重复,并且2)Java也可以代表一流功能(单方法类对象,请参见Guava的FunctionalJava函数F),但是语法太冗长而无法使用。
2012年

23

什么是函数式编程

目前,“函数式编程”有两种不同的定义:

较早的定义(源自Lisp)是函数编程是关于使用一流函数进行编程,即,将函数与其他任何值一样对待,以便您可以将函数作为参数传递给其他函数,并且函数可以在其返回值中返回函数。这最终会使用高阶函数,例如mapreduce(您可能听说过mapReduceGoogle大量使用的单个操作,并且毫不奇怪,它是近亲!)。.NET类型System.FuncSystem.Action在C#中提供高阶函数。尽管在C#中不建议使用currying,但是将其他函数作为参数接受的函数是常见的,例如,Parallel.For函数。

较年轻的定义(由Haskell推广)是函数式编程还涉及最小化和控制包括突变在内的副作用,即编写通过编写表达式来解决问题的程序。这通常被称为“纯函数式编程”。通过对称为“纯功能数据结构”的数据结构的完全不同的方法,可以实现这一点。一个问题是,将传统的命令式算法转换为使用纯功能数据结构通常会使性能降低10倍。Haskell是唯一幸存的纯函数式编程语言,但是这些概念已Linq通过.NET等库进入了主流编程领域。

我想在哪里使用它而不是非功能性编程

到处。现在,C#中的Lambda表现出了巨大的优势。C ++ 11具有lambda。现在没有理由不使用高阶函数。如果您可以使用F#之类的语言,您还将受益于类型推断,自动泛化,currying和部分应用程序(以及许多其他语言功能!)。

我认为C是一种非功能性编程语言是否正确?

是。C是一种过程语言。但是,通过使用函数指针和void *C语言,您可以获得函数式编程的一些好处。


您能否在这条线上详细说明一下,或分享一个示例:“一个问题是,将传统的命令式算法转换为使用纯功能数据结构通常会使性能降低10倍。”
捷豹2015年

1
几乎所有传统算法(例如,快速排序,LZW,LU分解,Prim's MST)本质上几乎总是固有的。特别是,它们永远不会从持久性中受益:它们始终仅在集合的最新版本上运行,并且从不重用旧版本。这使得它们非常适合使用传统的可变集合来实现,但是这意味着,如果您使用纯功能集合来实现它们,那么您将为持久性(即速度较慢)付出代价,但没有任何好处。
JD

1
取而代之的是,您必须更加仔细地寻找能够解决相同问题并确实受益于持久性的奇异算法,例如合并排序和Borůvka的MST。
JD

6

也许值得在CoDe Mag最近发布的F#“​​ 101”上查看这篇文章。

另外,达斯汀·坎贝尔Dustin Campbell)拥有一个出色的博客,他在该博客上发表了许多有关F#快速入门的文章。

希望您觉得这些有用:)

编辑:

另外,仅补充一点,我对函数式编程的理解是,一切都是函数,或者函数的参数,而不是实例/有状态的对象。但是我可能错了F#是我渴望进入的东西,但不要有时间!:)


4

统计学家约翰的示例代码未显示函数式编程,因为在进行函数式编程时,关键是该代码没有ASSIGNMENTS(record = thingConstructor(t)是赋值),并且没有SIDE EFFECTS(localMap.put(record)是带有副作用的语句) 。由于这两个约束,函数的所有操作都可以通过其参数和返回值完全捕获。如果您想使用C ++模拟功能语言,请按照看起来的方式重写Statistician的代码:

RT getOrCreate(const T something, 
                  const Function <RT <T >> ThingConstructor, 
                  const Map <T,RT <T >> localMap){
    返回localMap.contains(t)吗?
        localMap.get(t):
        localMap.put(t,thingConstructor(t));
}

由于没有副作用规则的结果,每一个语句是返回值(因此的一部分return第一),和每一个语句是一个表达式。在执行功能性程序设计的语言中,return隐含关键字,并且if语句的行为类似于C ++的?:运算符。

而且,所有内容都是不可变的,因此localMap.put必须创建一个localMap的新副本并返回它,而不是像普通的C ++或Java程序那样修改原始的localMap。根据localMap的结构,副本可以将指针重新使用到原始副本中,从而减少了必须复制的数据量。

函数式编程的一些优点包括以下事实:函数式程序更短,并且更容易修改函数式程序(因为没有要考虑的隐藏全局影响),并且更容易在函数式编程中正确设置程序。第一名。

但是,功能性程序往往运行缓慢(因为必须执行所有复制操作),并且它们往往无法与其他程序,操作系统进程或操作系统进行良好的交互,这些程序处理内存地址(低位字节序)字节块和其他机器特定的非功能位。非互操作性程度往往与功能纯度和类型系统的严格程度成反比。

较流行的功能语言具有非常非常严格的类型系统。在OCAML中,您甚至不能混合使用整数和浮点运算,也不能使用相同的运算符(+用于添加整数,+。用于添加浮点数)。根据您对类型检查器捕获某些类型的错误的能力的重视程度,这可能是优点还是缺点。

功能语言也往往具有很大的运行时环境。Haskell是一个例外(在编译时和运行时,GHC可执行文件几乎都与C程序一样小),但是SML,Common Lisp和Scheme程序始终需要大量内存。


1
我不同意,因为功能语言可以(并且像Erlang一样偶尔允许)允许单项分配,而没有副作用。但是,关于副作用的观点是正确的。但是,我不会更改代码,因为我认为说明混合语言中功能特性的优势比严格的纯洁更为重要。
约翰与华夫饼

1
功能编程的绝大多数是关于一流功能的使用,而不是关于副作用的禁止。我的测量结果表明,您关于运行时环境大小的陈述是错误的:GHC生成的二进制文件是OCaml和Standard ML(使用MLton)的两倍,是HLVM的20倍。
JD 2010年

1
不要将FP与没有副作用的混淆,例如DP与IP的混淆。那是FP。
谢尔比·摩尔三世

3

是的,您认为C是一种非功能性语言是正确的。C是一种过程语言。


3

我更喜欢使用函数式编程来保存自己的重复工作,方法是制作一个更抽象的版本,然后使用它。让我举个例子。在Java中,我经常发现自己创建映射来记录结构,从而编写了getOrCreate结构。

SomeKindOfRecord<T> getOrCreate(T thing) { 
    if(localMap.contains(thing)) { return localMap.get(thing); }
    SomeKindOfRecord<T> record = new SomeKindOfRecord<T>(thing);
    localMap = localMap.put(thing, record);
    return record; 
}

这种情况经常发生。现在,我可以用一种功能语言写

RT<T> getOrCreate(T thing, 
                  Function<RT<T>> thingConstructor, 
                  Map<T,RT<T>> localMap) {
    if(localMap.contains(thing)) { return localMap.get(thing); }
    RT<T> record = thingConstructor(thing);
    localMap = localMap.put(thing,record);
    return record; 
}

而且我永远不必再写一个新的,我可以继承它。但是我可以比继承做得更好,我可以在这件事的构造函数中说

getOrCreate = myLib.getOrCreate(*,
                                SomeKindOfRecord<T>.constructor(<T>), 
                                localMap);

(其中*是一种“将此参数保持打开状态”表示法,这是一种多变的现象)

然后本地的getOrCreate与如果我将整个内容写成一行而没有继承依赖性的情况完全相同。


2

如果您正在寻找有关F#的好的文字

专家F#由Don Syme共同编写。F#的创建者。他专门研究.NET中的泛型,因此可以创建F#。

F#是根据OCaml建模的,因此任何OCaml文本都将帮助您学习F#。


Don真的只为F#做泛型吗?我发现这令人惊讶!
JD 2010年

1

我发现什么是函数编程?有用

函数式编程是关于编写纯函数,关于我们尽可能地删除隐藏的输入和输出,以便我们尽可能多的代码仅描述输入和输出之间的关系。

偏好显式when参数

public Program getProgramAt(TVGuide guide, int channel, Date when) {
  Schedule schedule = guide.getSchedule(channel);

  Program program = schedule.programAt(when);

  return program;
}

过度

public Program getCurrentProgram(TVGuide guide, int channel) {
  Schedule schedule = guide.getSchedule(channel);

  Program current = schedule.programAt(new Date());

  return current;
}

功能语言积极地反对副作用。副作用是复杂性,复杂性是错误,错误是魔鬼。功能性语言也将帮助您抵御副作用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.