FP基础
由Mapreduce想到Function programming, 于是看到programming paradigms, 下面的链接进行了比较, 有兴趣可以认真研究一下...
http://en.wikipedia.org/wiki/Comparison_of_programming_paradigms
- Imperative programming – defines computation as statements that change a program state
- Functional programming – treats computation as the evaluation of mathematical functions and avoids state and mutable data
- Procedural programming, structured programming – specifies the steps the program must take to reach the desired state.
- Event-driven programming – the flow of the program is determined by events, such as sensor outputs or user actions (mouse clicks, key presses) or messages from other programs or threads.
- Object-oriented programming (OOP) – organizes programs as objects: data structures consisting of datafields and methods together with their interactions.
- Declarative programming – defines computation logic without defining its control flow.
- Automata-based programming – a program, or part, is treated as a model of a finite state machine or any other formal automata.
要注意的是, 编程语言和paradigms是两码事, 比如python, 可以写oop, 也可以写fp, sp, edp…
对于大部分语言都是一样, 语言只是工具, 怎样用看使用者.
现在很多人用c++, 于是大家都说自己在面向对象编程, 其实很多人根本就不懂什么是面向对象, 并不是你用c++就是面向对象编程.
paradigm, 范式, 在库恩的理论中,一个科学的范式(Paradigm)就是一套关于现实的假设,这套假设比其他假设更好地说明了当今的世界.
所以你到底使用的是什么paradigm取决于你对于现实的假设, 和世界观, 就是你看待问题的角度, 没有什么范式是绝对好或坏的, 只是看待世界的角度不同.
如果你是以面向过程的角度来思考问题的, 那么无论你拿什么语言写出的都是pp的代码, 所以切换paradigm不是换语言, 而是换一种思考问题的角度和方式.
Functional programming languages, especially purely functional ones such as the pioneering Hope, have largely been emphasized in academia rather than in commercial software development. However, prominent functional programming languages such as Common Lisp, Scheme,[2][3][4][5] ISLISP, Clojure, Racket,[6] Erlang,[7][8][9] OCaml,[10][11] Haskell,[12][13] Scala[14] and F#[15][16] have been used in industrial and commercial applications by a wide variety of organizations. Functional programming is also supported in some domain-specific programming languages like R (statistics),[17][18] Mathematica (symbolic math),[19] J and K (financial analysis)[citation needed] and XQuery/XSLT (XML).[20][21] Widespread domain-specific declarative languages like SQL and Lex/Yacc use some elements of functional programming, especially in eschewing mutable values.[22]
Functional Programming For The Rest of Us
http://www.defmacro.org/ramblings/fp.html
http://www.cnblogs.com/Zetazzz/archive/2011/02/04/1949197.html, 译文, 质量不高
这篇blog深入浅出的解释了FP, 做下笔记吧.
FP比较难于理解, 作者想用比较简单的方式来描述, 不过不太成功, 还是比较难于理解呵呵.
从plato的散步, 得出如下结论,
Plato suggested that everything in our world is just an approximation of perfection. He also realized that we understand the concept of perfection even though we never encountered it. He came to conclusion that perfect mathematical forms must live in another world and that we somehow know about them by having a connection to that "alternative" universe.
描述了现实和理想的差距, 现实世界中没有所谓的完美的东西, 找不到完全一样的两片树叶, 找不到一个完美的圆...但是人们却能够理解完美并且用数学等式把它表示出来, 这个确实是非常不可思议的事. 人怎么能够了解他从来没有见过的东西... 所以mathematics是很牛比的, 它可以用来描述现实中不存在的perfect
所以数学也是哲学, 因为他在现实中并不存在, 只是一种存在于大脑中的形式系统或演算系统... 所以也和哲学一样, 关键是提出问题而不是给出答案, 因为可以给出答案的就不是哲学问题, 而是现实问题.
Philosophy of mathematics is a very complex subject. Like most philosophical disciplines it is far more adept at posing questions rather than providing answers.
Much of the consensus revolves around the fact that mathematics is really a puzzle: we set up a set of basic non-conflicting principles and a set of rules on how to operate with these principles. We can then stack these rules together to come up with more complex rules. Mathematicians call this method a "formal system" or a "calculus".
让我们回到美国大萧条时期, 在大家都没有饭吃的时候, 在Princeton University有4个牛比的人天天大鱼大肉, Alonzo Church, Alan Turing, John von Neumann, and Kurt Gödel.
他们共同的兴趣就是形式系统, 而不是现实世界, 他们关注计算问题, 想回答下面的问题...
The four men were interested in formal systems. They didn't pay much heed to the physical world, they were interested in dealing with abstract mathematical puzzles instead.
Their puzzles had something in common: the men were working on answering questions about computation.
If we had machines that had infinite computational power, what problems would we be able to solve?
Could we solve them automatically?
Could some problems remain unsolved and why?
Would various machines with different designs be equal in power?
Alonzo Church提出一种叫做lamda演算的形式系统, 这个系统本质是一种在某种想象中的机器上的编程语言(Turing machine也是一样, 只不过更幸运, Turing想象中的机器模型后来被von Neumann实现了, 并在现在成为主流). function在Greek语中就是lambda, 所以lamda演算其实就是函数演算, 以函数作为函数的参数和返回值.
lambda calculus作为FP的理论基础, 现在也越来越被重视...
In cooperation with other men Alonzo Church developed a formal system called lambda calculus. The system was essentially a programming language for one of these imaginary machines. It was based on functions that took other functions as parameters and returned functions as results. The function was identified by a Greek letter lambda, hence the system's name. Using this formalism Alonzo was able to reason about many of the above questions and provide conclusive answers.
Independently of Alonzo Church, Alan Turing was performing similar work. He developed a different formalism (now referred to as the Turing machine), and used it to independently come to similar conclusions as Alonzo.
本来也许故事就到此结束了...可是二战爆发了, 军方对大规模计算有迫切的需求, 那个时候是为了调整火炮的精度, 这个背景下computer得到大力的发展
The first machine to solve ballistic tables was a Mark I built by IBM - it weighed five tons, had 750,000 parts and could do three operations per second.
In 1949 an Electronic Discrete Variable Automatic Computer (EDVAC) was unveiled and had tremendous success. It was a first example of von Neumann's architecture and was effectively a real world implementation of a Turing machine. For the time being Alonzo Church was out of luck.
大家采用了Turing的形式系统架构, 所以Alonzo Church运气不好, 成为了非主流...
一直到人工智能之父John McCarthy, 从新发现了它的价值, 并基于它实现了Lisp语言(implementation of Alonzo's lambda calculus that worked on von Neumann computers)
In late 1950s an MIT professor John McCarthy (also a Princeton graduate) developed interest in Alonzo Church's work. In 1958 he unveiled a List Processing language (Lisp). Lisp was an implementation of Alonzo's lambda calculus that worked on von Neumann computers! Many computer scientists recognized the expressive power of Lisp. In 1973 a group of programmers at MIT's Artificial Intelligence Lab developed hardware they called a Lisp machine - effectively a native hardware implementation of Alonzo's lambda calculus!
Functional programming只是lambda calculus的一个实现版本, 但无法涵盖所有的方面, 因为lambda calculus是理想中的理想系统, 而在物理世界中, 太多的限制.
Functional programming is a practical implementation of Alonzo Church's ideas. Not all lambda calculus ideas transform to practice because lambda calculus was not designed to work under physical limitations.
采用FP的益处
You're probably thinking that there's no way I can rationalize the monstrosity of a function above. When I was learning functional programming I was thinking that too. I was wrong. There are very good arguments for using this style. Some of them are subjective. For example, people claim that functional programs are easier to understand. I will leave out these arguments because every kid on the block knows that ease of understanding is in the eye of the beholder. Fortunately for me, there are plenty of objective arguments.
Unit Testing
Since every symbol in FP is final, no function can ever cause side effects. You can never modify things in place, nor can one function modify a value outside of its scope for another function to use (like a class member or a global variable). That means that the only effect of evaluating a function is its return value and the only thing that affects the return value of a function is its arguments.
没有side effects, 所以每个funciton都可以便于UT, 一般OO, 如果function里面有很多DB, I/O, 就无法进行独立UT
Debugging
If a functional program doesn't behave the way you expect it to, debugging it is a breeze. You will always be able to reproduce your problem because a bug in a functional program doesn't depend on unrelated code paths that were executed before it. In an imperative program a bug resurfaces only some of the time. Because functions depend on external state produced by side effects from other functions you may have to go through a series of steps in no way related to the bug. In a functional program this isn't the case - if a return value of a function is wrong, it is always wrong, regardless of what code you execute before running the function.
Once you reproduce the problem, getting to the bottom of it is trivial. It is almost pleasant. You break the execution of your program and examine the stack. Every argument in every function call in the stack is available for your inspection, just like in an imperative program.
Walking through the stack you look at arguments passed to functions and their return values. The minute a return value doesn't make sense you step into the offending function and walk through it. You repeat this recursively until the process leads you to the source of the bug!
这个应该是FP最大的好处, 可以把程序员从当前的测试和debug噩梦中解救出来...尤其c,c++程序员应该深有体会, 大型系统的debug有时候那种生不如死的感觉...
最主要就是FP执行过程中, 不会受外界干扰, 所以没有不确定性, issue出现一次, 就会每次出现.
而现在主流语言的debug, 最大的问题就是常常issue不可重现, 因为在执行过程中有太多的外部因素会影响到结果, 比如class member or a global variable, 所以有很大的不确定性.
所以FP的quality更容易保证, UT也更有效, debug也更容易.
Concurrency
A functional program is ready for concurrency without any further modifications. You never have to worry about deadlocks and race conditions because you don't need to use locks! No piece of data in a functional program is modified twice by the same thread, let alone by two different threads. That means you can easily add threads without ever giving conventional problems that plague concurrency applications a second thought!
If this is the case, why doesn't anybody use functional programs for highly concurrent applications? Well, it turns out that they do. Ericsson designed a functional language called Erlang for use in its highly tolerant and scalable telecommunication switches.
The concurrency story doesn't stop here. If your application is inherently single threaded the compiler can still optimize functional programs to run on multiple CPUs. Take a look at the following code fragment:
String s1 = somewhatLongOperation1(); String s2 = somewhatLongOperation2(); String s3 = concatenate(s1, s2);
In a functional language the compiler could analyze the code, classify the functions that create strings s1 and s2 as potentially time consuming operations, and run them concurrently.
这个是FP的又一大优势, 因为程序执行不依赖于, 外部状态, 所以并发不需要任何race conditions或lock.
而且更牛比的是, FP在执行时compiler会将程序自动的并发处理. 这个对当前, 多核处理器而言, 无疑可以大大提高性能.
Hot Code Deployment, 不停机的代码热部署
An ideal situation is updating relevant parts of the code without stopping any part of the system at all. In an imperative world this isn't possible. Consider unloading a Java class at runtime and reloading a new definition. If we were to do that every instance of a class would become unusable because the state it holds would be lost.
In a functional program all state is stored on the stack in the arguments passed to functions. This makes hot deployment significantly easier! In fact, all we'd really have to do is run a diff between the code in production and the new version, and deploy the new code. The rest could be done by language tools automatically! If you think this is science fiction, think again. Erlang engineers have been upgrading live systems without stopping them for years.
Machine Assisted Proofs and Optimizations
An interesting property of functional languages is that they can be reasoned about mathematically. Since a functional language is simply an implementation of a formal system, all mathematical operations that could be done on paper still apply to the programs written in that language.
本身就是形式系统, 便于直接进行数学计算
FP的基本特性
Higher Order Functions, 高阶函数, 函数的参数也是函数
Functions that operate on other functions (accept them as arguments) are called higher order functions.
How, and when, do you use higher order functions?
When you see that a particular piece of code is repeated, you break it out into a function (fortunately they still teach this in schools). If you see that a piece of logic within your function needs to behave differently in different situations, you break it out into a higher order function.
举个例子, 如下
Imperative Programming
abstract class MessageHandler { void handleMessage(Message msg) { // ... msg.setClientCode(getClientCode()); // ... sendMessage(msg); } abstract String getClientCode(); // ... } class MessageHandlerOne extends MessageHandler { String getClientCode() { return "ABCD_123"; } } class MessageHandlerTwo extends MessageHandler { String getClientCode() { return "123_ABCD"; } }
Function Programming
class MessageHandler { void handleMessage(Message msg, Function getClientCode) { // ... Message msg1 = msg.setClientCode(getClientCode()); // ... sendMessage(msg1); } // ... } String getClientCodeOne() { return "ABCD_123"; } String getClientCodeTwo() { return "123_ABCD"; } MessageHandler handler = new MessageHandler(); handler.handleMessage(someMsg, getClientCodeOne);
Currying, 名字来源于Haskell Curry
虽然名字比较吓人, 其实是个非常简单的操作, 以减少参数为目的的函数封装
怎么用?
一般做开发的时候都讲究通用性, 希望写一份代码可以用于所有的场合
但是使用的时候, 每次都从通用的代码开始用, 会比较麻烦...
所以在FP中会先定义一个通用的function, F(x,y,z,w)
而对于某些场景, 比如x,y都是固定的, 其实只需要指定z, w, 所以每次调用都指定4个参数, 会觉得很麻烦...
做法就是做层封装,
J(z, w) {F(x=1, y=2, z, w)}
这样调用就很方便, 这个过程就叫做curry…
当然你也可以说, 这个是一种adapter pattern
Adapter pattern is best known when applied to the "default" abstraction unit in Java - a class. In functional languages the pattern is applied to functions. The pattern takes an interface and transforms it to another interface someone else expects. Here's an example of an adapter pattern:
int pow(int i, int j); int square(int i) { return pow(i, 2); }
In academic circles this trivial technique is called currying (after a logician Haskell Curry who performed mathematical acrobatics necessary to formalize it). Because in FP functions (as opposed to classes) are passed around as arguments, currying is used very often to adapt functions to an interface that someone else expects. Since the interface to functions is its arguments, currying is used to reduce the number of arguments (like in the example above).
Functional languages come with this technique built in.
square = int pow(int i, 2);
As you can see, we've simply created a wrapper for the original function. In FP currying is just that - a shortcut to quickly and easily create wrappers.
Lazy Evaluation, 懒惰的
Lazy (or delayed) evaluation is an interesting technique that becomes possible once we adopt a functional philosophy.
In programming language theory, lazy evaluation or call-by-need[1] is an evaluation strategy which delays the evaluation of an expression until its value is needed (non-strict evaluation) and which also avoids repeated evaluations (sharing).
Haskell is an example of a delayed evaluation language. In Haskell you are not guaranteed that anything will be executed in order (or at all) because Haskell only executes code when it's required.
We will discuss the advantages here.
Optimization
Lazy evaluation provides a tremendous potential for optimizations. A lazy compiler thinks of functional code exactly as mathematicians think of an algebra expression - it can cancel things out and completely prevent execution, rearrange pieces of code for higher efficiency, even arrange code in a way that reduces errors, all guaranteeing optimizations won't break the code.
Abstracting Control Structures
Lazy evaluation provides a higher order of abstraction that allows implementing things in a way that would otherwise be impossible.
Infinite Data Structures
Lazy languages allow for definition of infinite data structures, something that's much more complicated in a strict language. For example, consider a list with Fibonacci numbers.
Disadvantages,无法保证执行顺序
Of course there ain't no such thing as a free lunch(tm). Lazy evaluation comes with a number of disadvantages. Mainly that it is, well, lazy. Many real world problems require strict evaluation. For example consider the following:
System.out.println("Please enter your name: "); System.in.readLine();
In a lazy language you have no guarantee that the first line will be executed before the second!
Closures, 闭包
这个确实是比较难于理解的概念, 如果基于原文, 你基本不可能理解什么是closure…所以只能google…
http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html
http://www.cnblogs.com/tqsummer/archive/2011/01/24/1943314.html
闭包(Closure)是词法闭包(Lexical Closure)的简称, 闭包是由函数和与其相关的引用环境组合而成的实体
OO编程范式中的对象是“整合了函数的数据对象”,那么闭包就是“整合了数据的函数对象”
借用一个非常好的说法来做个总结:对象是附有行为的数据,而闭包是附有数据的行为。
可以实现闭包的语言必须具备一下特性:
- 函数是一阶值(First-class value),即函数可以作为另一个函数的返回值或参数,还可以作为一个变量的值。
- 函数可以嵌套定义,即在一个函数内部可以定义另一个函数
上面是定义, 下面看个例子,
powerFn本身是一个function, 但是他用到了外部变量power, 所以powerFN和power的结合的实体, 就是一个闭包. 包含funciton和他的上下文.
这儿有趣的是, 你在调用square的时候, 作为makePowerFn参数的power本应该早就不存在了(因为makePowerFn调用结束, power就会被抛出栈), 而实际上这儿确可以得到power的值.
这就取决于闭包的实现, 它保存了powerFn函数当时的上下文.
Function makePowerFn(int power) { int powerFn(int base) { return pow(base, power); } return powerFn; } Function square = makePowerFn(2); square(3); // returns 9
Closures是怎样实现的? 这些数据不再被简单的作用域和生命周期所约束, 所以他们一定不做stack上, 他们必须被实现在heap上. 所以Closure被实现为a function, 加上an additional reference指向环境变量.
是不是有点象面向对象?数据和操作的结合, 这点上面已经说过
The first observation is that local variables are no longer limited to simple scope rules and have an undefined lifetime. The obvious conclusion is that they're no longer stored on the stack - they must be stored on the heap instead. A closure, then, is implemented just like a function we discussed earlier, except that it has an additional reference to the surrounding variables.
闭包有啥用? 更好的抽象, 简化代码, 加强模块化, 不外乎这些...
Closures和Curry
会不会觉得curry和closures有些相似?
上面的例子也可以用curry简单的达到同样的目的Function square( int base) { return pow (base, 2)}
对于这个, 我觉得, Closures和Curry其实不是一个层面的概念,
Closures是定义了一种实体, 类似oo里面的对象的概念, 从数据和方法结合的角度
Curry是定义了一种操作和方法, Curry可以用Closures去实现, 也可以不用, 参考上面的例子.
Continuations, 继续执行
我们通常知道的function, 比如下面的例子, example调用add
在example的代码段执行到add, 发现是function, 跳转到add的代码段执行
执行完, 将局部变量pop退栈, 然后将返回值付给example的局部变量i, 最后返回到example的代码段, 继续执行
发现下个函数是square, 再继续上面的过程
而所谓的continuation, 当add执行完, 不用返回到example的代码段, 而是直接跳转到square的代码段, 并将add返回值作为输入, 这样效率上应该高些
怎么实现?
传统的函数在add里面你是不知道下个函数的代码段指针的, 所以如果要实现continuation, 只需要在调用add的时候, 把调用序列也作为参数传入, 这样add就不需要返回到example, 而可以直接跳转.
When we learned about functions we only learned half truths based on a faulty assumption that functions must return their value to the original caller.
In this sense continuations are a generalization of functions. A function must not necessarily return to its caller and may return to any part of the program.
A "continuation" is a parameter we may choose to pass to our function that specifies where the function should return.
The description may be more complicated than it sounds. Take a look at the following code:
int example() {
int i = add(5, 10); int j = square(i); }
int j = add(5, 10, square); //continuation
我们可以用CPS的方法去写所有的程序, 因为普通函数其实就是continuation的一种特殊形式, 即跳转到caller, 而实际上很多编译器就是这么做优化的
We can write entire programs in CPS(Continuation Passing Style), where every function takes an extra continuation argument and passes the result to it. We can also convert any program to CPS simply by treating functions as special cases of continuations (functions that always return to their caller). This conversion is trivial to do automatically (in fact, many compilers do just that).
CPS的方式不需要stack, 我们只需要简单的将局部变量放到任一memory里面即可.
stack主要用于嵌套调用, 因为需要不断的保持调用过程中所有function的执行环境, 以便下层function返回后可以继续执行.
但对于CPS, 没有返回, 只是一直执行下去, 那么就没有保留之前环境的必要, 所以确实不需要stack
CPS version needs no stack! No function ever "returns" in the traditional sense, it just calls another function with the result instead.
We don't need to push function arguments on the stack with every call and then pop them back, we can simply store them in some block of memory and use a jump instruction instead.
We'll never need the original arguments - they'll never be used again since no function ever returns!
In what situations are continuations useful? Usually when you're trying to simulate state in an application of inherently stateless nature to ease your life. A great application of continuations are web applications.
Pattern Matching, 用多函数来替代函数中多分支
从下面的例子可以看出什么意思, 你可以把函数中的多分支拆分成多函数, 当然下面的例子, 你根本看不出这样做的好处, 但是当if或swith的分支达到几百个的时候, 你就知道维护这样一个庞大的条件判断是一件多么恐怖的事情. 而用这种方式可以简单的把分支拆开, 以便于管理.
Let's dive into pattern matching with an example. Here's a Fibonacci function in Java:
int fib(int n) { if(n == 0) return 1; if(n == 1) return 1; return fib(n - 2) + fib(n - 1); }
And here's an example of a Fibonacci function in our Java-derived language that supports pattern matching:
int fib(0) { return 1; } int fib(1) { return 1; } int fib(int n) { return fib(n - 2) + fib(n - 1); }
When is pattern matching useful?
In a surprisingly large number of cases! Every time you have a complex structure of nested ifs, pattern matching can do a better job with less code on your part.
Another benefit of pattern matching is that if you need to add or modify conditions, you don't have to go into one huge function.
You simply add (or modify) appropriate definitions. This eliminates the need for a whole range of design patterns from the GoF book. The more complex your conditions are, the more pattern matching will help you. Once you're used to it, you start wondering how you ever got through your day without it.
这儿说的让Gof的很多设计模式变的没有价值, 最典型的就是工厂模式, 通过类分装和拆分条件分支, 而对于FP, 通过更简单的pattern matching就可以做到