Open luckyscript opened 8 years ago
这是来百度后的新人技术分享,这是个大体的提纲,分析的比较简单,因为我觉得自己对FP也存在一知半解。 什么是函数式编程(What?)
这是来百度后的新人技术分享,这是个大体的提纲,分析的比较简单,因为我觉得自己对FP也存在一知半解。
函数式编程中的函数这个术语不是指计算机中的函数(实际上是子程序),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。
函数是一等公民(first-class),可以在任何地方定义,在函数内或函数外,可以作为函数的参数和返回值,可以对函数进行组合。
指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。
在JavaScript中,引入了函数。但显然JS中的函数可以访问、修改全局变量(或定义在函数外的变量),如下
var a = 5; function fun(){ a = 10 } fun();
JS中要想保证函数无副作用这项特性,只能依靠编程人员的习惯,即
1,函数入口使用参数运算,而不修改它
2,函数内不修改函数外的变量,如全局变量
3,运算结果通过函数返回给外部(出口)
var a = 5; function fun(c){ return c + 5 } fun(a);
引用透明的概念与函数的副作用相关,且受其影响。 如果程序中两个相同值得表达式能在该程序的任何地方互相替换,而不影响程序的动作,那么该程序就具有引用透明性。它的优点是比非引用透明的语言的语义更容易理解,不那么晦涩。纯函数式语言没有变量,所以它们都具有引用透明性。
高阶函数应该是指至少满足下列条件之一的函数:
Haskell的精髓 所谓惰性求值就是指“需要的时候再去求” 举个例子:
通过将表达式包装成一个thunk实现的 f(g x),实际上给f传递的参数就是一个类似于包装成(_ -> (g x))的一个thunk 然后在真正需要计算g x的时候才会调用这个thunk 事实上这个thunk里面还包含一个boolean表示该thunk是否已经被计算过(若已经被计算过,则还包含一个返回值),用来防止重复计算 ES6里面的Generator函数其实就是用了惰性求值。
如下的函数应该如何求值?
var x = 1; function f(m){ return m * 2; } f(x + 5)
应该先求1+5 然后求 6_2 还是先求出式子(x+5)_2然后代入1 如果想深究的话 可以在维基百科上搜索求值策略
(x+5)_2
c语言采用第一种 而Haskell采用第二种 所以Haskell中应该是这样的一个过程
var thunk = function () { return x + 5; }; function f(thunk){ return thunk() * 2; }
trunk其实就是传名调用的一个策略,用来保存表达式。 如果想知道惰性求值的原理可以读一下sicp中关于流式计算的那章
equal_list :: [a] -> [a] -> Bool equal_list list_1 list_2 = case (list_1, list_2) of ([], []) -> False ([], _ ) -> False (_ , []) -> False (x: xs, y: ys) -> (x == y) && (xs `equal_list` ys)
模式匹配是函数式编程中一个比较重要的概念,这也很方便的使我们做一些操作。
lambda比较火,目前很多语言都开始引入lambda的概念。 js的匿名函数(anonymity function) es6的箭头函数(arrow function) python中的lambda java8中的lambda
个人认为是为了达到纯函数的目的,所采用的方法。 比如,Haskell中实现random函数。计算机中的任何random方法除了unix内核中的random是真随机外,其余的都是伪随机。所谓伪随机,就是要提供一个种子。而如果Haskell中定义了一个种子,那么就明显不是纯函数了。所以Haskell要做的就是把这个种子向下传递,Haskell中的random方法不仅仅返回random的值,同时返回种子(但是不是显式),那么如何将种子传下去呢?这就是monad的作用了。 再比如,io操作是无法避免的会有状态的改变的,所以Haskell中也提供了大量的与io相关的monad。
由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。
函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。这样做的好处就是测试和调试的时候就会非常容易。
由于不变性,多个线程之前就不共享状态,不会导致资源的竞争,也就不会出现死锁的情况。所以函数式编程目前广泛被用在并发编程上(concurrency),比如著名的Scala。
由于没有状态的改变,所以纯函数式编程语言无法实现循环(loop)的。我们平时接触的for/while循环都是需要可变的状态作为跳出循环的条件。 所以函数式编程中只能用递归(recursive)来解决迭代(iteration)问题。以阶乘为例
iteration实现阶乘:
function fact(n) { var acc = 1; for (var i = 1; i <= n; i++) { acc = acc * i; } return acc; } fact(10);
recursive实现阶乘:
function fact(n) { if (n == 1) { return 1; } else { return n*fact(n-1); } } fact(10);
函数式编程是面向数学的抽象,更接近人的语言,代码简洁,更容易被理解。
阶乘的Haskell的实现
fact :: Int -> Int fact n = case n of 1 -> 1 _ -> n * fact (n-1)
function fib(n) { if (n == 1) { return 1; } else if (n == 2) { return 1; } else { return fib(n - 1) + fib (n - 2); } } fib(12);
尾递归(tail-recursive)解决
function fib(n, acc1, acc2) { if(n == 1) { return acc1; } else if(n == 2) { return acc2 + acc1; } else { return fib(n-1, acc2 + acc1, acc1) } } fib(60,1,1);
但是,学习FP和OOP的思想是我们每个编程爱好者需要做的。虽然当中有糟粕,但是学习这些还是大有裨益的。 如果你不想去看Haskell的FP,也不想花大精力去研究java的OOP,那么javascript是个不错的选择。
好吧,我想说,php是最好的语言(逃。
沙发
666
http://www.jianshu.com/p/509505d3bd50
6的不行哟
You should introduce lambda calculus.
Functional programming languages are based on lambda calculus. http://dev.stephendiehl.com/fun/lambda_calculus.html
函数式编程中的函数这个术语不是指计算机中的函数(实际上是子程序),而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。
特点(fectures)
函数是一等公民(first-class),可以在任何地方定义,在函数内或函数外,可以作为函数的参数和返回值,可以对函数进行组合。
纯函数( Pure Function )
非纯函数 ( Impure Function )
副作用(side effect)
指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量)或修改参数。
在JavaScript中,引入了函数。但显然JS中的函数可以访问、修改全局变量(或定义在函数外的变量),如下
JS中要想保证函数无副作用这项特性,只能依靠编程人员的习惯,即
1,函数入口使用参数运算,而不修改它
2,函数内不修改函数外的变量,如全局变量
3,运算结果通过函数返回给外部(出口)
引用透明( referential transparent )
引用透明的概念与函数的副作用相关,且受其影响。 如果程序中两个相同值得表达式能在该程序的任何地方互相替换,而不影响程序的动作,那么该程序就具有引用透明性。它的优点是比非引用透明的语言的语义更容易理解,不那么晦涩。纯函数式语言没有变量,所以它们都具有引用透明性。
高阶函数(high-order function)
高阶函数应该是指至少满足下列条件之一的函数:
惰性求值(lazy evaluation)
Haskell的精髓 所谓惰性求值就是指“需要的时候再去求” 举个例子:
通过将表达式包装成一个thunk实现的 f(g x),实际上给f传递的参数就是一个类似于包装成(_ -> (g x))的一个thunk 然后在真正需要计算g x的时候才会调用这个thunk 事实上这个thunk里面还包含一个boolean表示该thunk是否已经被计算过(若已经被计算过,则还包含一个返回值),用来防止重复计算 ES6里面的Generator函数其实就是用了惰性求值。
求值策略
如下的函数应该如何求值?
应该先求1+5 然后求 6_2 还是先求出式子
(x+5)_2
然后代入1 如果想深究的话 可以在维基百科上搜索求值策略trunk
c语言采用第一种 而Haskell采用第二种 所以Haskell中应该是这样的一个过程
trunk其实就是传名调用的一个策略,用来保存表达式。 如果想知道惰性求值的原理可以读一下sicp中关于流式计算的那章
模式匹配(pattern match)
模式匹配是函数式编程中一个比较重要的概念,这也很方便的使我们做一些操作。
lambda
lambda比较火,目前很多语言都开始引入lambda的概念。 js的匿名函数(anonymity function) es6的箭头函数(arrow function) python中的lambda java8中的lambda
Monad
个人认为是为了达到纯函数的目的,所采用的方法。 比如,Haskell中实现random函数。计算机中的任何random方法除了unix内核中的random是真随机外,其余的都是伪随机。所谓伪随机,就是要提供一个种子。而如果Haskell中定义了一个种子,那么就明显不是纯函数了。所以Haskell要做的就是把这个种子向下传递,Haskell中的random方法不仅仅返回random的值,同时返回种子(但是不是显式),那么如何将种子传下去呢?这就是monad的作用了。 再比如,io操作是无法避免的会有状态的改变的,所以Haskell中也提供了大量的与io相关的monad。
优点(good-part)
由于命令式编程语言也可以通过类似函数指针的方式来实现高阶函数,函数式的最主要的好处主要是不可变性带来的。没有可变的状态,函数就是引用透明(Referential transparency)的和没有副作用(No Side Effect)。
函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。这样做的好处就是测试和调试的时候就会非常容易。
由于不变性,多个线程之前就不共享状态,不会导致资源的竞争,也就不会出现死锁的情况。所以函数式编程目前广泛被用在并发编程上(concurrency),比如著名的Scala。
由于没有状态的改变,所以纯函数式编程语言无法实现循环(loop)的。我们平时接触的for/while循环都是需要可变的状态作为跳出循环的条件。 所以函数式编程中只能用递归(recursive)来解决迭代(iteration)问题。以阶乘为例
iteration实现阶乘:
recursive实现阶乘:
函数式编程是面向数学的抽象,更接近人的语言,代码简洁,更容易被理解。
阶乘的Haskell的实现
递归带来的问题(recursive's problem)
尾递归(tail-recursive)解决
2
缺陷
总结
但是,学习FP和OOP的思想是我们每个编程爱好者需要做的。虽然当中有糟粕,但是学习这些还是大有裨益的。 如果你不想去看Haskell的FP,也不想花大精力去研究java的OOP,那么javascript是个不错的选择。
好吧,我想说,php是最好的语言(逃。