函数式编程[编辑]
编程范式 |
---|
函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算(lambda calculus)为该语言最重要的基础。而且,λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。
比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
在函数式编程中,函数是第一类对象,意思是说一个函数,既可以作为其它函数的参数(输入值),也可以从函数中返回(输入值),被修改或者被分配给一个变量。
历史[编辑]
函数式编程的理论基础是Lambda演算,其本身是一种数学的抽象但不是编程语言。另一个组合逻辑是比它更加古老和基础的数学根基。两者都是为了更好的表达数学基础才被开发的。[1]
早期的函数式语言例如Lisp,是由 John McCarthy在麻省理工学院,于20世纪50年代后期开发的,运行在大型IBM机(IBM 700/7000系列)上。[2]Lisp最早引入了函数式的很多特性,最开始的lisp是多范式语言,并且随着新的范式的发展,越来越多的编程风格得到了支持。后来分支出来的语言,例如Scheme,Clojure ,Dylan和Julia等分支,试图简化Lisp,使它围绕一个功能核心,而Common Lisp旨在保留lisp的原始范式特征。[3]
而发明于1956年的IPL语言,一般被认为是第一个基于计算机的函数式编程语言。[4] 它是一种用于操纵符号列表的汇编式语言。它有一个生成器的概念,相当于一个接受函数作为参数的函数,并且,由于它是汇编级语言,代码可以是数据,因此IPL可以被视为具有更高阶函数。但是,它在很大程度上依赖于改变列表的结构和类似的命令性编程的功能。也就是说,并不是完全的现在所谓的函数式编程。
Kenneth E. Iverson在20世纪60年代早期开发了APL ,在他1962年出版的《A Programming Language》[5]一书中有介绍。 APL给John Backus的FP提供了巨大的影响。 在20世纪90年代早期,Iverson和Roger Hui创造了J语言。 在20世纪90年代中期,以前曾与Iverson合作过的Arthur Whitney创建了K语言,后者在金融行业中与其派生出来的Q语言一起被商业化使用。
John Backus在他1977年的图灵奖颁奖演讲《可以从冯·诺依曼式的编程风格中解放出来的程序设计和功能风格及其程序代数》中,展示了他提出的FP[6]。他将函数式编程定义为通过“组合形式”以分层方式构建,允许“程序代数”; 在现代语言中,这意味着功能性程序遵循组合性原则 。Backus的论文推广了函数式编程的研究,虽然它强调的是函数级编程而不是现在所说的lambda演算风格。
1973年ML语言由爱丁堡大学的 Robin Milner发明。同年,David Turner在圣安德鲁斯大学开发SASL语言。在20世纪70年代的爱丁堡,Burstall和Darlington开发了NPL语言。[7] NPL基于Kleene递归方程 ,并在他们的程序转换工作中首次引入。[8] 然后Burstall,MacQueen和Sannella将ML的多态类型检查结合起来,产生了Hope语言。[9]ML最终发展成几种语言,其中最常见的是OCaml和Standard ML。 同时,如有影响力的Lambda paper和经典的1985年教科书《Structure and Interpretation of Computer Programs》中所描述的,Scheme的发展,Lisp的功能子集和不完全的函数式语言分支,将函数式编程的影响力提升到更广泛的范围,让更多的编程语言社区接触到它们。
在20世纪80年代, Per Martin-Löf开发了直觉类型论(也称为构造类型论),它将函数式编程与表现为类型依赖的数学证明联系起来。这导致了交互式定理证明的新方法的产生,并影响了后续的函数式编程语言的发展。 David Turner开发的惰性求值函数式语言Miranda最初出现在1985年,对后来的Haskell有很强的影响。 由于Miranda是非公开的语言,所以Haskell社区于1987年开始达成共识,以形成函数式编程研究的开放标准,对标准的实现自1990年以来一直在进行中。
最近,它在基于CSG几何框架构建的OpenSCAD语言的参数CAD中得到了应用,虽然它无法区分左值和右值,导致了不熟悉函数式编程的用户混淆。 [10]
函数式编程更加现代一些的例子包括Clean、Clojure、Erlang、Haskell、Miranda、Scheme等。
应用[编辑]
工业[编辑]
函数式编程长期以来在学术界流行,但几乎没有工业应用。[11]:page 11然而,最近几种函数式编程语言已经在商业或工业系统中使用。[12]例如,Erlang编程语言由瑞典公司Ericsson在20世纪80年代后期开发,最初用于实现容错电信系统。此后,它已在Nortel,Facebook,ÉlectricitédeFrance和WhatsApp等公司作为创建一系列应用程序的语言。[13][14]Lisp的Scheme分支被用作早期Apple Macintosh计算机上的几个应用程序的基础,并且最近已应用于诸如训练模拟软件和望远镜控制等方向。OCaml于20世纪90年代中期推出,已经在金融分析,驱动程序验证,工业机器人编程和嵌入式软件静态分析等领域得到了商业应用。Haskell虽然最初是作为一种研究语言,也已被一系列公司应用于航空航天系统,硬件设计和网络编程等领域。
其他在工业中使用的函数式编程语言包括Scala[15]、F#(两者都是功能性面对对象编程的混合,支持纯函数和命令式编程)、Wolfram语言、Lisp、Standard ML和Clojure。
教育[编辑]
教育方面,函数式编程一直受到了很大的重视,很多学校使用函数式编程来教授算法和几何的相关概念[16]。
速度和空间上的顾虑[编辑]
函数式编程常被认为严重耗费CPU和存储器资源[17] 。主因有二:
- 在实现早期的函数式编程语言时并没有考虑过效率问题。
- 面向函数式编程特性(如保证函数参数不变性等)的独特数据结构和算法。
典型的函数式编程语言[编辑]
纯函数式编程语言[编辑]
强静态类型
弱类型
非纯函数式编程语言[编辑]
强静态类型
静态类型
强动态类型
弱类型
其他函数式编程语言[编辑]
参考文献[编辑]
- ^ Haskell Brooks Curry; Robert Feys. Combinatory Logic. North-Holland Publishing Company. 1958 [10 February 2013]. (原始内容存档于2020-04-09).
- ^ McCarthy, John. History of Lisp. In ACM/SIGPLAN History of Programming Languages Conference. June 1978: 217–223 [2019-05-12]. doi:10.1145/800025.808387. (原始内容存档于2008-06-07).
- ^ Guy L. Steele; Richard P. Gabriel. The Evolution of Lisp (PDF). In ACM/SIGPLAN Second History of Programming Languages. February 1996: 233–330 [2019-05-12]. ISBN 978-0-201-89502-5. doi:10.1145/234286.1057818. (原始内容存档 (PDF)于2020-11-12).
- ^ The memoir of Herbert A. Simon (1991), Models of My Life pp.189-190 ISBN 0-465-04640-1 claims that he, Al Newell, and Cliff Shaw are "...commonly adjudged to be the parents of [the] artificial intelligence [field]," for writing Logic Theorist, a program that proved theorems from Principia Mathematica automatically. To accomplish this, they had to invent a language and a paradigm that, viewed retrospectively, embeds functional programming.
- ^ ISBN 9780471430148
- ^ Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs (PDF). [2019-05-12]. (原始内容存档 (PDF)于2020-11-08).
- ^ R.M. Burstall. Design considerations for a functional programming language. Invited paper, Proc. Infotech State of the Art Conf. "The Software Revolution", Copenhagen, 45–57 (1977)
- ^ R.M. Burstall and J. Darlington. A transformation system for developing recursive programs. Journal of the Association for Computing Machinery 24(1):44–67 (1977)
- ^ R.M. Burstall, D.B. MacQueen and D.T. Sannella. HOPE: an experimental applicative language. Proc. 1980 LISP Conference, Stanford, 136–143 (1980).
- ^ Make discovering assign() easier!. OpenSCAD. [2019-05-12]. (原始内容存档于2020-09-28).
- ^ Odersky, Martin; Spoon, Lex; Venners, Bill. Programming in Scala: A Comprehensive Step-by-step Guide 2nd. Artima. December 13, 2010: 883/852 [2019-05-12]. ISBN 978-0-9815316-4-9. (原始内容存档于2016-04-30).
- ^ Ray, Baishakhi; Posnett, Daryl; Devanbu, Premkumar; Filkov, Vladimir. A large-scale study of programming languages and code quality in GitHub. Communications of the ACM. 2017-09-25, 60 (10): 92. doi:10.1145/3126905 (英语).
- ^ Piro, Christopher. Functional Programming at Facebook. CUFP 2009. 2009 [2009-08-29]. (原始内容存档于2009-10-17).
- ^ 1 million is so 2011 页面存档备份,存于互联网档案馆 // WhatsApp blog, 2012-01-06: "the last important piece of our infrastracture is Erlang"
- ^ Momtahan, Lee. Scala at EDF Trading: Implementing a Domain-Specific Language for Derivative Pricing with Scala. CUFP 2009. 2009 [2009-08-29]. (原始内容存档于2009-10-17).
- ^ Emmanuel Schanzer of Bootstrap在TWiT.tv上的节目《Triangulation》的采访(英文)
- ^ Larry C. Paulson. ML for the Working Programmer. Cambridge University Press. 28 June 1996 [10 February 2013]. ISBN 978-0-521-56543-1. (原始内容存档于2020-04-09).
外部链接[编辑]
|
|
1. 同步函数与异步函数
什么是同步函数?
什么是异步函数?
它们在线程中执行时会对线程有何影响?
一个线程如何与一个异步执行的函数进行同步?
依据微软的MSDN上的解说:
(1) 同步函数:当一个函数是同步执行时,那么当该函数被调用时不会立即返回,直到该函数所要做的事情全都做完了才返回。
(2) 异步函数:如果一个异步函数被调用时,该函数会立即返回尽管该函数规定的操作任务还没有完成。
(3) 在一个线程中分别调用上述两种函数会对调用线程有何影响呢?
当一个线程调用一个同步函数时(例如:该函数用于完成写文件任务),如果该函数没有立即完成规定的操作,则该操作会导致该调用线程的挂起(将CPU的使用权交给系统,让系统分配给其他线程使用),直到该同步函数规定的操作完成才返回,最终才能导致该调用线程被重新调度。
当一个线程调用的是一个异步函数(例如:该函数用于完成写文件任务),该函数会立即返回尽管其规定的任务还没有完成,这样线程就会执行异步函数的下一条语句,而不会被挂起。那么该异步函数所规定的工作是如何被完成的呢?当然是通过另外一个线程完成的了啊;那么新的线程是哪里来的呢?可能是在异步函数中新创建的一个线程也可能是系统中已经准备好的线程。
(4)一个调用了异步函数的线程如何与异步函数的执行结果同步呢?
为了解决该问题,调用线程需要使用“等待函数”来确定该异步函数何时完成了规定的任务。因此在线程调用异步函数之后立即调用一个“等待函数”挂起调用线程,一直等到异步函数执行完其所有的操作之后,再执行线程中的下一条指令。
我们是否已经发现了一个有趣的地方呢?!就是我们可以使用等待函数将一个异步执行的函数封装成一个同步函数。
2.同步调用与异步调用
操作系统发展到今天已经十分精巧,线程就是其中一个杰作。操作系统把 CPU 处理时间划分成许多短暂时间片,在时间 T1 执行一个线程的指令,到时间 T2 又执行下一线程的指令,各线程轮流执行,结果好象是所有线程在并肩前进。这样,编程时可以创建多个线程,在同一期间执行,各线程可以“并行”完成不同的任务。
在单线程方式下,计算机是一台严格意义上的冯·诺依曼式机器,一段代码调用另一段代码时,只能采用同步调用,必须等待这段代码执行完返回结果后,调用方才能继续往下执行。有了多线程的支持,可以采用异步调用,调用方和被调方可以属于两个不同的线程,调用方启动被调方线程后,不等对方返回结果就继续执行后续代码。被调方执行完毕后,通过某种手段通知调用方:结果已经出来,请酌情处理。
计算机中有些处理比较耗时。调用这种处理代码时,调用方如果站在那里苦苦等待,会严重影响程序性能。例如,某个程序启动后如果需要打开文件读出其中的数据,再根据这些数据进行一系列初始化处理,程序主窗口将迟迟不能显示,让用户感到这个程序怎么等半天也不出来,太差劲了。借助异步调用可以把问题轻松化解:把整个初始化处理放进一个单独线程,主线程启动此线程后接着往下走,让主窗口瞬间显示出来。等用户盯着窗口犯呆时,初始化处理就在背后悄悄完成了。程序开始稳定运行以后,还可以继续使用这种技巧改善人机交互的瞬时反应。用户点击鼠标时,所激发的操作如果较费时,再点击鼠标将不会立即反应,整个程序显得很沉重。借助异步调用处理费时的操作,让主线程随时恭候下一条消息,用户点击鼠标时感到轻松快捷,肯定会对软件产生好感。
异步调用用来处理从外部输入的数据特别有效。假如计算机需要从一台低速设备索取数据,然后是一段冗长的数据处理过程,采用同步调用显然很不合算:计算机先向外部设备发出请求,然后等待数据输入;而外部设备向计算机发送数据后,也要等待计算机完成数据处理后再发出下一条数据请求。双方都有一段等待期,拉长了整个处理过程。其实,计算机可以在处理数据之前先发出下一条数据请求,然后立即去处理数据。如果数据处理比数据采集快,要等待的只有计算机,外部设备可以连续不停地采集数据。如果计算机同时连接多台输入设备,可以轮流向各台设备发出数据请求,并随时处理每台设备发来的数据,整个系统可以保持连续高速运转。编程的关键是把数据索取代码和数据处理代码分别归属两个不同的线程。数据处理代码调用一个数据请求异步函数,然后径自处理手头的数据。待下一组数据到来后,数据处理线程将收到通知,结束 wait 状态,发出下一条数据请求,然后继续处理数据。
异步调用时,调用方不等被调方返回结果就转身离去,因此必须有一种机制让被调方有了结果时能通知调用方。在同一进程中有很多手段可以利用,笔者常用的手段是回调、event 对象和消息。
回调:回调方式很简单:调用异步函数时在参数中放入一个函数地址,异步函数保存此地址,待有了结果后回调此函数便可以向调用方发出通知。如果把异步函数包装进一个对象中,可以用事件取代回调函数地址,通过事件处理例程向调用方发通知。
event : event 是 Windows 系统提供的一个常用同步对象,以在异步处理中对齐不同线程之间的步点。如果调用方暂时无事可做,可以调用 wait 函数等在那里,此时 event 处于 nonsignaled 状态。当被调方出来结果之后,把 event 对象置于 signaled 状态,wait 函数便自动结束等待,使调用方重新动作起来,从被调方取出处理结果。这种方式比回调方式要复杂一些,速度也相对较慢,但有很大的灵活性,可以搞出很多花样以适应比较复杂的处理系统。
消息:借助 Windows 消息发通知是个不错的选择,既简单又安全。程序中定义一个用户消息,并由调用方准备好消息处理例程。被调方出来结果之后立即向调用方发送此消息,并通过 WParam 和 LParam 这两个参数传送结果。消息总是与窗口 handle 关联,因此调用方必须借助一个窗口才能接收消息,这是其不方便之处。另外,通过消息联络会影响速度,需要高速处理时回调方式更有优势。
如果调用方和被调方分属两个不同的进程,由于内存空间的隔阂,一般是采用 Windows 消息发通知比较简单可靠,被调方可以借助消息本身向调用方传送数据。event 对象也可以通过名称在不同进程间共享,但只能发通知,本身无法传送数据,需要借助 Windows 消息和 FileMapping 等内存共享手段或借助 MailSlot 和 Pipe 等通信手段。
异步调用原理并不复杂,但实际使用时容易出莫名其妙的问题,特别是不同线程共享代码或共享数据时容易出问题,编程时需要时时注意是否存在这样的共享,并通过各种状态标志避免冲突。Windows 系统提供的 mutex 对象用在这里特别方便。mutex 同一时刻只能有一个管辖者。一个线程放弃管辖权后,另一线程才能接管。当某线程执行到敏感区之前先接管 mutex,使其他线程被 wait 函数堵在身后;脱离敏感区之后立即放弃管辖权,使 wait 函数结束等待,另一个线程便有机会光临此敏感区。这样就可以有效避免多个线程进入同一敏感区。
由于异步调用容易出问题,要设计一个安全高效的编程方案需要比较多的设计经验,所以最好不要滥用异步调用。同步调用毕竟让人更舒服些:不管程序走到哪里,只要死盯着移动点就能心中有数,不至于象异步调用那样,总有一种四面受敌、惶惶不安的感觉。必要时甚至可以把异步函数转换为同步函数。方法很简单:调用异步函数后马上调用 wait 函数等在那里,待异步函数返回结果后再继续往下走。
3.如何写一个异步函数
我们平常编程写的函数 几乎都是同步调用函数,那么我们如何写一个异步执行的函数呢?!我想这个问题也许是哪些比较喜欢专研的程序员或者具有专研精神的人士回提出的问题吧!我们很多人已经习惯了windows系统提供的一些异步机制,使用这些异步机制我们很快的就能实现一些异步操作甚至可以很容易的实现一个异步执行的函数;但是我们研究过实现一个“异步函数”的本质吗?!
在单线程的系统中,所以的指令执行都是顺序执行的,这就暗示了如果一个函数A中调用了函数B,则A必须等到B执行后才能继续执行A中剩下的代码。
在多线程中,如果我们有一个threadA线程,在该线程中调用了一个函数C,而该C函数我们想将它实现成异步执行的,而异步执行必须要有多线程支持;如果我们在Windows中编写程序,创建一个线程是很简单只要使用
HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId );
函数就可以创建一个线程。
那么我们按如下方式可以实现一个异步的FuncC函数:
(1)先把你要异步完成的工作单独写成要给函数,如
DWORD WINAPI AsyncronousThread(
LPVOID lpParameter // thread data
)
{
.....
}
(2)在函数FuncC中使用CreateThtread函数将(1)中的函数创建一成一个线程,然后直接返回。
void FuncC(void)
{
.....
CreateThread(....,AsyncronousThread,...);
return;
}
当然,写一个异步函数的方法很多,但是一个本质不会变,就是必须要依据多线程才能实现。
4.回调与异步机制
回调函数是应用程序提供给Windows系统DLL或其它DLL调用的函数,一般用于截获消息、获取系统信息或处理异步事件。应用程序把回调函数的地址指针告诉DLL,而DLL在适当的时候会调用该函数。回调函数必须遵守事先规定好的参数格式和传递方式,否则DLL一调用它就会引起程序或系统的崩溃。通常情况下,回调函数采用标准WindowsAPI的调用方式,即__stdcall,当然,DLL编制者可以自已定义调用方式,但客户程序也必须遵守相同的规定。在__stdcall方式下,函数的参数按从右到左的顺序压入堆栈,除了明确指明是指针或引用外,参数都按值传递,函数返回之前自己负责把参数从堆栈中弹出。
程序在调用一个函数(function)时(通常指api).相当于程序(program)呼叫(Call)了一个函数(function)关系表示如下:
call(调用)
program --------------------→ dll
程序在调用一个函数时,将自己的函数的地址作为参数传递给程序调用的函数时(那么这个自己的函数称回调函数).需要回调函数的 DLL 函数往往是一些必须重复执行某些操作的函数.关系表示如下:
call(调用)
program --------------------→ dll
↑ ¦
¦_______________________________¦
callback(回调)
当你调用的函数在传递返回值给回调函数时,你就可以利用回调函数来处理或完成一定的操作。至于如何定义自己的回调函数,跟具体使用的API函数有关,很多不同类别的回调函数有各种各样的参数,有关这些参数的描述一般在帮助中有说明回调函数的参数和返回值等.其实简单说回调函数就是你所写的函数满足一定条件后,被DLL调用!
Windows 系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是 Windows 的基本控制手段,是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的 WParam 和 LParam 相当于函数的参数,应用程序可以主动发送消息,更多情况下是坐等 Windows 发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而应用程序一旦启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。其实,应用程序之间也可以形成这种回调。假如进程 B 收到进程 A 发来的消息,启动了一段代码,其中又向进程 A 发送消息,这就形成了回调。这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。利用消息也可以构成狭义回调。把回调函数地址换成窗口 handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借 API 函数 SendMessage 向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借 API 函数 PostMessage 发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。
回调用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数据时,除了完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。其实回调和API非常接近,他们的共性都是跨层调用的函数。但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调,在需要调用时,只需引用这个函数指针和相关的参数指针。 其实:回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。
转发:http://blog.163.com/lyzaily@126/blog/static/42438837200952751954922/
转发:https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B
函数[编辑]
此条目需要精通或熟悉数学的编者参与及协助编辑。 |
函数在数学中为两不为空集的集合间的一种对应关系:输入值集合中的每项元素皆能对应唯一一项输出值集合中的元素。例如实数{displaystyle x}对应到其平方{displaystyle x^{2}}的关系就是一个函数,若以{displaystyle 3}作为此函数的输入值,所得的输出值便是{displaystyle 9}。
为方便起见,一般做法是以符号{displaystyle f,g,h}等等来指代一个函数。若函数{displaystyle f}以{displaystyle x}作为输入值,则其输出值一般写作{displaystyle f(x)},读作'f of x' 。上述的平方函数关系写成数学式记为{displaystyle f(x)=x^{2}}。函数的概念并不局限于数之间的映射关系,例如若定义函数{displaystyle operatorname {Capital} ()}为每个国家当前的首都,那么给予输入值韩国就会输出唯一值首尔:{displaystyle operatorname {Capital} (mathrm {Korea} )=mathrm {Seoul} }。 气温的分布也能用函数表达,以时间和地点作为参量输入,以该时该地的温度作为输出。表达函数有多种方式,例如解析法是用数学式表达两个变量之间的对应关系,图像法是用坐标系上的函数图形表达两个变量之间的对应关系,列表法用表格表达两个变量之间的对应关系。
现代数学中[1],函数所有输入值的集合被称作该函数的定义域,而其输出值所存在的集合称为到达域。其中值域特指该函数的输出值集合,意即上域包含了值域,值域为上域的子集。通常输入值称作函数的参数或参量,输出值称作函数的值。函数将有效的输入值变换为唯一的输出值,同一输入总是对应同一输出,但反之未必成立。因此如{displaystyle mathrm {Root} (x)=pm {sqrt {x}}}这样的表达式并没有定义出一个函数,因为输出值有两个可能。定义函数时需确定每一个输入值只对应唯一输出值,因此必须明确地选择一个平方根。例如定义{displaystyle mathrm {Posroot} (x)={sqrt {x}}},亦即对于任何非负输入值,选择其非负平方根作为函数值。
函数可以看作机器或黑箱,通常最常见的函数的参数和函数值都是数字,其对应关系用函数式表示,函数值可以通过直接将参数值代入函数式得到。{displaystyle f(x)=x^{2}},{displaystyle x}的平方即是函数值。也可以将函数很简单的推广到与多个参量相关的情况。例如{displaystyle g(x,y)=xy}有两个参量{displaystyle x}和{displaystyle y},以乘积{displaystyle xy}为值。将这两个输入看作一个有序对{displaystyle (x,y)}。{displaystyle g}即为以这个有序对{displaystyle (x,y)}作参数的函数,而函数值是{displaystyle xy}。函数能被抽象定义为某种数学关系,由于其定义的一般性,在几乎所有的数学分支都是基础概念。一些领域中比如在λ演算中,函数可以是作为一个原始概念而不像在集合论般有所定义。在大部分的数学领域内,术语对应、映射、变换通常是函数的近义词。不过某些情况这些术语可能有别的特定意思,例如在拓扑学中一个映射有时被定义成一个连续函数。
目录
定义[编辑]
从输入值集合{displaystyle X}到可能的输出值集合{displaystyle Y}的函数{displaystyle f}(记作{displaystyle f:X o Y})是{displaystyle X}与{displaystyle Y}的关系,满足如下条件:
- {displaystyle f}是完全的:对集合{displaystyle X}中任一元素{displaystyle x}都有集合{displaystyle Y}中的元素{displaystyle y}满足{displaystyle xfy}({displaystyle x}与{displaystyle y}是{displaystyle f}相关的)。即,对每一个输入值,{displaystyle y}中都有与之对应的输出值。
- {displaystyle f}是多对一的:若{displaystyle f(x)=y}且{displaystyle f(x)=z},则{displaystyle y=z}。即,多个输入可以映射到一个输出,但一个输入不能映射到多个输出。
定义域中任一{displaystyle x}在到达域中唯一对应的{displaystyle y}记为{displaystyle f(x)}。
比上面定义更简明的表述如下:从{displaystyle X}映射到{displaystyle Y}的函数{displaystyle f}是{displaystyle X}与{displaystyle Y}的直积{displaystyle X imes Y}的子集。{displaystyle X}中任一{displaystyle x}都与{displaystyle Y}中的{displaystyle y}唯一对应,且有序对{displaystyle (x,y)}属于{displaystyle f}。
{displaystyle X}与{displaystyle Y}的关系若满足条件(1),则为多值函数。函数都是多值函数,但多值函数不都是函数。{displaystyle X}与{displaystyle Y}的关系若满足条件(2),则为偏函数。函数都是偏函数,但偏函数不都是函数。除非特别指明,本条目中的“函数”总是指同时满足以上两个条件的关系。 考虑如下例子:
函数的判别[编辑]
除了利用函数的定义之外,还可以利用竖直判别法,即函数的图形与任何一条平行于 y 轴的直线不能有一个以上的交点。
历史[编辑]
函数这个数学名词是莱布尼兹在1694年开始使用的,用来描述跟曲线相关的一个量,如曲线的斜率或者曲线上的某一点。莱布尼兹所指的函数现在被称作可导函数,数学家之外的普通人一般接触到的函数即属此类。对于可导函数可以讨论它的极限和导数,此两者描述了函数输出值的变化同输入值变化的关系,是微积分学的基础。中文的“函数”一词由清朝数学家李善兰译出。其《代数学》书中解释:“凡此变量中函(包含)彼变量者,则此为彼之函数”。
- 1718年,约翰·伯努利把函数定义为“一个变量的函数是指由这个变量和常量以任何一种方式组成的一种量。”
- 1748年,伯努利的学生欧拉在《无穷分析引论》一书中说:“一个变量的函数是由该变量和一些数或常量以任何一种方式构成的解析表达式”,例如{displaystyle f(x)=sin(x)+x^{2}}。
- 1775年,欧拉在《微分学原理》一书中又提出了函数的一个定义:“如果某些量以如下方式依赖于另一些量,即当后者变化时,前者本身也发生变化,则称前一些量是后一些量的函数。”
- 19世纪的数学家开始对数学的各个分支进行形式化。维尔斯特拉斯倡议将微积分学建立在算术,而不是几何的基础上,这种主张较趋向于欧拉的定义。
- 函数的定义得以扩展之后,数学家便能对一些“奇怪”的数学对象进行研究,例如处处不可导的连续函数。这些函数曾经被认为只具有理论价值,迟至20世纪初时它们仍被视作“怪物”。稍后,人们发现这些函数在对如布朗运动之类的物理现象进行建模时有重要的作用。
- 到19世纪末,数学家开始尝试利用集合论来进行数学的形式化。他们试图将每一个数学对象都定义为集合。狄利克雷给出了现代正式的函数定义(参见下文#正式定义)。在他的定义下,函数被视作数学关系的特例。然而对于实际应用的情况,现代定义和欧拉定义的区别可以忽略不计。
表示方法[编辑]
- 描述法
- 表格
- 公式法
- 图形
分段函数[编辑]
分段函数(德语:Abschnittsweise definierte Funktion),在定义域内不同部分上,有不同的解析表达式,这样的函数通常叫分段函数。分段实际上是一种表达函数的方式,而不是函数本身的一个特征,但是具有额外的限定,可以描述函数的本质。
函数图形[编辑]
函数{displaystyle f}在平面上的图形是点对{displaystyle (x,f(x))}的集合,其中{displaystyle x}取遍定义域上的所有成员。函数图形可以帮助理解证明一些定理。 注意函数图形可以有两个定义:一是三元组{displaystyle (X,Y,G)},其中{displaystyle X}是函数的定义域,{displaystyle Y}是函数的到达域,{displaystyle G}是关系的图;二是索性以关系的图定义。用第二个定义则函数{displaystyle f}等于其图形。
单射、满射与双射函数[编辑]
- 单射函数,将不同的输入值映射到不同的函数值。即:若{displaystyle x}和{displaystyle y}属于定义域,则仅当{displaystyle x=y}时有{displaystyle f(x)=f(y)}。
- 满射函数,其值域即为其到达域。即:对于映射{displaystyle f}的到达域中之任意{displaystyle y},都存在至少一个{displaystyle x}满足{displaystyle f(x)=y}。
- 双射函数,既是单射的又是满射的函数。也叫一一对应、双射。双射函数经常被用于表明集合{displaystyle X}和{displaystyle Y}是等势的,即有一样的基数。如果在两个集合之间可以建立一个一一对应,则说这两个集合等势。
定义域与值域、陪域[编辑]
- 定义域:原像集,自变量的取值集合。
- 值域:像集,因变量的取值集合。
- 陪域:值域所属的全集。
像和原像[编辑]
元素{displaystyle xin X}在{displaystyle f}之下的像就是{displaystyle f(x)}。
子集{displaystyle Asubset X}在{displaystyle f}之下的像,是以{displaystyle A}的元素的像所组成的集合,为{displaystyle Y}的一个子集,即
- {displaystyle f(A):={f(x):xin A}}。
注意{displaystyle f}的值域就是定义域{displaystyle X}的像{displaystyle f(X)}。在#正式定义一节的最后例子中,{displaystyle {2,3}}在{displaystyle f}的像是{displaystyle f({2,3}={c,d}},而{displaystyle f}的值域是{displaystyle {c,d}}。
根据此定义,{displaystyle f}可引申成为由{displaystyle X}的幂集(由{displaystyle X}的子集组成的集)到{displaystyle Y}的幂集之函数,亦记作{displaystyle f}。
子集{displaystyle Bsubset Y}在{displaystyle f}的原像(或逆像)是如下定义的{displaystyle X}的子集:
- {displaystyle f^{-1}(B):={xin X:f(x)in B}}。
沿用同一例子,我们可以看到{displaystyle {a,b}}的原像是{displaystyle f^{-1}({a,b})=varnothing },即空集。
根据此定义,{displaystyle f^{-1}(x)}是由{displaystyle Y}的幂集到{displaystyle X}的幂集之函数。
以下是{displaystyle f}及{displaystyle f^{-1}}的一些特性:
- {displaystyle f(A_{1}cup A_{2})=f(A_{1})cup f(A_{2})};
- {displaystyle f(A_{1}cap A_{2})subseteq f(A_{1})cap f(A_{2})};
- {displaystyle f(B_{1}cup B_{2})=f^{-1}(B_{1})cup f^{-1}(B_{2})};
- {displaystyle f^{-1}(B_{1}cap B_{2})=f^{-1}(B_{1})cap f^{-1}(B_{2})};
- {displaystyle f^{-1}(f(B))subseteq B};
- {displaystyle f^{-1}(f(A))supseteq A}。
这些特性适合定义域的任意子集{displaystyle A,A_{1}}及{displaystyle A_{2}}和到达域的任意子集{displaystyle B,B_{1}}及{displaystyle B_{2}},甚至可推广到任意子集群的交集和并集。
函数范例[编辑]
- 首都之于国家(若不把多首都国[1] 页面存档备份,存于互联网档案馆 计算在内)。
- 每个自然数{displaystyle n}的平方{displaystyle n^{2}}是{displaystyle n}的函数。
- 对数函数。{displaystyle ln x}是正实数{displaystyle x}的函数。注意,虽然可以把对数函数推广到复数情况,但结果就不是函数了,而是多值函数。
- 对每个在{displaystyle mathbb {R} ^{2}}平面上的点,其和原点{displaystyle (0,0)}的距离是确定的。
常用的数学函数包括多项式函数、根式函数、幂函数、对数函数、有理函数、三角函数、反三角函数等。它们都是初等函数。非初等函数(或特殊函数)包括伽马函数和贝塞尔函数等。
函数的分类[编辑]
函数可分为
复合函数[编辑]
函数{displaystyle f:X o Y}及{displaystyle g:Y o Z}的复合函数是
- {displaystyle gcirc f:X o Z:f(gcirc f)(x)=g(f(x))}。
举例,飞机在{displaystyle t}时刻的高度是{displaystyle h(t)},而高度{displaystyle x}处的氧气浓度是{displaystyle c(x)},则在{displaystyle t}时刻飞机周围的氧气浓度是 {displaystyle (ccirc h)(t)}
若{displaystyle Ysubset X}则 {displaystyle f}可自我复合;此时复合函数可记作{displaystyle f^{2}}(不要与三角学的符号混淆)。函数的幂的定义是对自然数{displaystyle n}有
- {displaystyle f^{n+1}=f^{n}circ f=fcirc f^{n}}
反函数[编辑]
对一个函数f: X→Y,若值域Y中任何一个元素y的原象是唯一的,那么这个函数就被称为是双射的。对任意的y∈Y到它的原象ƒ−1(y)的映射,我们称之为f的反函数,记为f−1。
举一个反函数的例子,比如ƒ(x) = x3,它的反函数是ƒ−1(x) = {displaystyle {sqrt[{3}]{x}}} 。同样,2x的反函数是x2。反函数是一个函数,它能够“抵消”它的原函数,并具有和原函数相同的单调性。参见逆映射。
函数的限制及扩张[编辑]
给出{displaystyle Y}的子集{displaystyle X}以及函数
- {displaystyle f:Y ightarrow Z},
则
- {displaystyle f|_{X}:X ightarrow Z}
- {displaystyle f|_{X}(x)=f(x)}
称为{displaystyle f}在{displaystyle X}的限制。
反之,若给出函数
- {displaystyle g:X o Z}
当一个定义在{displaystyle Y}的函数{displaystyle f:Y o Z}有{displaystyle f|_{X}=g},{displaystyle f}就是{displaystyle g}的扩张。
点态运算[编辑]
设函数f: X → R及g: X → R有X为共同的定义域及环R为共同的到达域。我们可以定义“函数和”f + g: X → R及“函数积”f×g:X → R如下:
- (f + g)(x) := ƒ(x) + g(x);
- f×g(x) := ƒ(x)×g(x);
对于所有X中的x。
这样子我们得出一个函数组成的环。这是一个抽象性扩张的例子,由此我们从较简单的结构得出更复杂的。
若然用别的代数结构A代替R,得出的由X到A的函数集会类似地拥有和A相同的代数结构。
歧义函数[编辑]
歧义函数,也称多值函数,指有输出值多于一个的情况。例如,4的平方根可以是2或者-2,而两者的平方皆是4。
严格来说,歧义函数不完全算是函数,因为数学函数的定义对于一个输入值只能有唯一一个输出值。实际上,这样的“函数”通常被称为关系式。复变函数理论采用黎曼面处理函数多值的困境。
一元函数[编辑]
设 D 是实数集 R 中的非空子集,称映射 f : D -> R 为定义在 D 上的一元函数。
多元函数[编辑]
多元函数(n-元函数)是指输入值为n-元组的函数。或者说,若一函数的输入值域为n个集合的笛卡尔积的子集,这函数就是n-元函数。例如,距离函数dist((x,y))是一个二元函数,输入值是由两个点组成的序对。另外,多复变函数(即输入值为复数的多元组)是一个重要的数学课题。
在抽象代数中,算子其实都是函数,如乘法"*"是个二元函数:当我们写x*y时,其实是用上了*(x,y)的中缀表示法。
函数式程序设计是一个以函数概念为中心的重要理论范式,其中的运算对象为多元函数,基本语法基于λ演算,而函数的复合则采用代换来完成。特别地,通过一种称为柯里化的变换,可将多元函数变换为一元函数。
可计算和不可计算函数[编辑]
所有从整数到整数的可计算函数的个数是可数的,这是因为所有可能的算法个数是可数的。从整数到整数的函数个数要更多些-和实数个数一样多,也就是说是等势的。这说明有些从整数到整数的函数是不可计算的。关于不可计算函数,请参看停机问题和莱斯定理,OEIS中有一个经典的例子: A102288。
范畴论观点下的函数[编辑]
一个范畴包括一组对象与一组态射,每一个态射是个三元组(X, Y, f),X称为源对象(定义域的类比),Y称为目标对象(到达域的类比),而源对象与目标对象是范畴内的对象。基于这种解释,可以把函数看作集合范畴里面的态射。