C#函数式编程

编程语言范式html

常见的编程范式有命令式编程(Imperative programming函数式编程逻辑式编程;sql

许多现存的编程语言均可基于其计算模型加以分类,纳入某些语言族,或者属于某种编程范式。按照不一样的规则,能够有多种分类的方法,并且不一样的学者对某些语言的具体归属也有不一样的意见。编程

给出一种系谱:数组

说明式(Declarative )                   命令式( Imperative 缓存

  函数式  Lisp, ML, Haskell                冯诺依曼 C, Ada, Fortran闭包

  数据流  ld, Val                         脚本式 Perl, Python, PHP架构

  逻辑式  Prolog                         面向对象 Smalltalk, C++, Java, C#编程语言

  基于模板 XSLT函数式编程

有些编程范式并不能按以上的方法进行分类,好比:元编程,泛型编程。函数

一种语言并非只从属于一种编程范式,有些语言自己就是为支持多范式设计的;

好比:Lisp就同时支持函数式编程、面向对象、元编程。

命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令)表达式(内存引用和算术运算)和控制语句(跳转指令);

 

函数式编程

定义

In computer science, functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data.

函数式编程是一种编程模型,他将计算机运算看作是数学中函数的计算,而且避免了状态以及变量的概念。<Wiki>

函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式

函数式编程最重要的基础是 λ 演算(lambda calculus)。并且λ演算的函数能够接受函数看成输入(参数)和输出(返回值)。和指令式编程相比,函数式编程的函数的计算比指令的执行更重要。和过程化编程相比,函数式编程的函数的计算可随时调用。纯函数式编程不须要变量;

 

语言族

函数式编程中最古老的例子可能要数1958年被创造出来的LISP,当年的Lisp因为各类设计缺陷(内存损耗、闭包问题、生成程序执行效率等)没能发展好。较现代的例子包括Haskell、Clean、Erlang 和Miranda等。

现代编程语言,如C#、Python、Ruby、Scala等等,它们都受到了函数式编程语言的影响,好比C#中的lamada表达式、Linq。

基于JVM实现的Lisp方言如Scala, Clojure也是愈来愈受关注,这里所谓的Lisp方言,主要是由于语法上沿用了Lisp中的S表达式

基于.net平台的有F#,微软的首个函数式编程语言。<MSDN>

 

不一样语言的抽象层次

                                                   计算

              C#         ----->     对象

              Python      ----->    函数式

              C语言      ----->    函数 (面向过程)

              汇编语言

         计算机硬件  ----->    指令                 计算机

 

函数式复兴

Anders Hejlsberg,C#编程语言的首席架构师,2010年关于《编程语言的发展趋势及将来方向》演讲

从一个数组中找出全部的偶数

List<int> list = new List<int> { 1,2,3,4,5,6,7};

常规的命令式写法:

    List<int> ret = new List<int>();

    foreach (var item in list)

    { if (item % 2 == 0)

          ret.Add(item); }

声明式的写法: var ret = list.Where((x) => x % 2 == 0);

 

多核与并行

         使用命令式编程语言写程序时,咱们常常会编写如x = x + 1这样的语句,此时咱们大量依赖的是可变状态,或者说是“变量”,它们的值能够随程序运行而改变。可变状态很是强大,但随之而来的即是被称为“反作用”的问题,例如一个无需参数的void方法,它会根据调用次数或是在哪一个线程上进行调用对程序产生影响,它会改变程序内部的状态,从而影响以后的运行效果。而在函数式编程中则不会出现这个状况,由于全部的状态都是不可变的。事实上对函数式编程的讨论更像是数学、公式,而不是程序语句,如x = x + 1对于数学家来讲,彷佛只是个永不为真的表达式而已。

函数式编程十分容易并行,由于它在运行时不会修改任何状态,所以不管多少线程在运行时均可以观察到正确的结果。假如两个函数彻底无关,那么它们是并行仍是顺序地执行便没有什么区别。

 

函数式编程特性                             与技术

     函数是一等公民                        闭包

     高阶函数                                  惰性求值

     递归                                         缓存技术

    不可变状态                               尾调用消除

    柯里化                                      内存回收

 

C#函数式支持

         Linq涉及的C#语言特性:隐式类型、匿名类型、初始化器、迭代器、委托、泛型、泛型委托、匿名方法、Lamada表达式。

函数对象必须是某种委托类型. 在C#中,咱们能够定义强类型的委托类型或泛型的委托类型,委托能够表明跟这个委托类型有相同参数的方法(静态方法,类方法)的引用.

在使用LINQ的时候咱们能够常常看到高阶函数。举个例子,若是你想将一个已有的序列使用一些函数转换为一个新的序列,你将使用相似LINQ的select函数(函数做为输入):

var squares = numbers.Select( num => num*num );

 

函数是一等公民

         对象是面向对象的第一型,那么函数式编程也是同样,函数是函数式编程的第一型。

咱们在函数式编程中努力用函数来表达全部的概念,完成全部的操做。

在面向对象编程中,咱们把对象传来传去,那在函数式编程中,咱们要作的是把函数传来传去。

函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。

函数能够在任何地方定义,在函数内或函数外,能够做为函数的参数和返回值,能够对函数进行组合。

高阶函数:能接收函数作参数的函数。

1:函数自身接受一个或多个函数做为输入参数;

2:函数自身能输出(返回)一个函数;

不可变状态

   纯函数式编程语言中的变量也不是命令式编程语言中的变量,即存储状态的单元,而是代数中的变量,即一个值的名称。变量的值是不可变的;

   函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,使得单元测试和调试都更容易。

递归

   因为变量不可变,纯函数编程语言没法实现循环,这是由于For循环使用可变的状态做为计数器,而While循环DoWhile循环须要可变的状态做为跳出循环的条件。所以在函数式语言里就只能使用递归来解决迭代问题,这使得函数式编程严重依赖递归。

递归定义的计算的Scala代码以下:

def fact(n: Int):Int= {

if(n == 0) return 1

n * fact(n-1)

}

C#代码

Public int Fact(int n)

{

 int acc = 1;

for(int k = 1; k <= n; k++){

acc = acc * k;}

}

尾递归

若是一个函数中全部递归形式的调用都出如今函数的末尾,咱们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特色是在回归过程当中不用作任何操做,这个特性很重要,由于大多数现代的编译器会利用这种特色自动生成优化的代码(将尾递归转化为迭代);

柯里化(Currying和部分(偏)函数(Partial Function)

是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术。

主要功能是提供了强大的动态函数建立方法,经过调用另外一个函数并为它传入要柯里化(currying)的函数和必要的参数而获得。通俗点说就是利用已有的函数,再建立一个动态的函数,该动态函数内部仍是经过已有的函数来发生做用。

柯里化就是一个函数在参数没给全时返回另外一个函数,返回的函数的参数正好是余下的参数。好比:你制定了x和y, 如2的3次方,就返回8, 若是你只制定x为2,y没指定, 那么就返回一个函数:2的y次方, 这个函数只有一个参数:y。

curry就是对高阶函数(就是一种对过程的抽象 参考map它就是一个抽象的过程)的降阶处理。

2大特性:

  • 匿名函数
  • 每一个函数只有1个参数

将多个参数的函数进行拆分,拆成多个只有一个参数的函数。为何要拆分,λ 演算。

示例:

常规的写法:Func<int, int, int> Add = (x, y) => x + y;

拆分:      Func<int, Func<int, int>> Add = x => y => x + y;

输入一个参数,返回一个具备一个参数的函数,接着再调用返回的函数,就完成整个调用。

调用:

         var add2 = Add(3);

     var ret = add2(4);

写成一行:

var ret = Add(3)(4);

或者不重写,只要为原来的方法加一个扩展方法:

public static Func<T1,Func<T2,T3>> Currey<T1,T2,T3>(this Func<T1,T2,T3> func)

{

    return x => y => func(x,y);

}

这样就能够对C#标准的GenralAdd(int x,int y)方法执行Currey转换为部分(偏)函数了:

Func<int, int, int> Add = GenralAdd;

         var CurreyedAdd = Add.Currey()(3)(4);

示例:好比咱们常常须要执行SQL语句,固然须要使用SqlConnection,而后附加上对应的SQL语句,为此咱们能够开发一个简单的函数,用来简化这一过程:

         Func<SqlConnection, Func<String, DataSet>> ExecSql = x => y =>

         {

              using (x)

              {

                   x.Open();

                   var com = x.CreateCommand();

                   DataSet ds = new DataSet();

                   com.CommandText = y;

                   SqlDataAdapter adapter = new SqlDataAdapter(com);

                   adapter.Fill(ds);

                   return ds;

               }

    };

调用:

         var esql = ExecSql(new SqlConnection("xxx"));

         var rds = esql("select xxxx from xxx");

         rds = esql("select ffff from ffff");

 

若是想先传入Sql语句再传入SqlConnection:

Func<String, Func<SqlConnection, DataSet>> ExecSqlT = x => y => ExecSql(y)(x);

看一个函数:

             static Func<int, int> GetAFunc()

        {

            var myVar = 1;

            Func<int, int> inc = delegate(int var1)

            {

                myVar = myVar + 1;

                return var1 + myVar;

            };

            return inc;

        }

 

以下调用输入什么结果:

            var inc = GetAFunc();

            Console.WriteLine(inc(5));

            Console.WriteLine(inc(6));

 

闭包

         闭包是能够包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。

“闭包” 一词来源于如下二者的结合:要执行的代码块(因为自由变量被包含在代码块中,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(做用域)。<百度百科>

 

C#中实现闭包,实际就是经过类,封装变量和方法,提高生命周期。

        static void Closure1()

        {

            List<Action> actions = new List<Action>();

            for (int i = 0; i < 10; i++)

            {

                int copy = i;

                actions.Add(() => Console.WriteLine(copy));

            }

            foreach (Action action in actions)

action();

        }

 

        static void Closure2()

        {

            int copy;

            List<Action> actions = new List<Action>();

            for (int i = 0; i < 10; i++)

            {

                copy = i;

                actions.Add(() => Console.WriteLine(copy));

            }

            foreach (Action action in actions)

action();

        }

 

缓存技术

怎样使用闭包来实现缓存。若是咱们建立了一个用于缓存的接收函数就能够实现缓存,并返回一个在一段时间内缓存结果的新函数。如下列表显示的例子:

            public static Func<T> Cache<T>(this Func<T> func, int cacheInterval)

            {

                var cachedValue = func();

                var timeCached = DateTime.Now;

                Func<T> cachedFunc = () => {

                if ((DateTime.Now - timeCached).Seconds >= cacheInterval)

                {

                        timeCached = DateTime.Now;

                        cachedValue = func();

                     }

                     return cachedValue;

                };

                return cachedFunc;

            }

变量 cacheInterval, cachedValue 和 timeCached 绑定到缓存的函数并做为函数的一部分。这个可让咱们记住最后的值并确认被缓存多长时间。

 

下面的例子中咱们能够看到如何使用这个扩展来缓存函数值和返回当前时间:

Func<DateTime> now = () => DateTime.Now;

            Func<DateTime> nowCached = now.Cache(4);

            Console.WriteLine("\tCurrent time\tCached time");

            for (int i = 0; i < 20; i++)

            {

                Console.WriteLine("{0}.\t{1:T}\t{2:T}", i + 1, now(), nowCached());

                Thread.Sleep(1000);

            }

 

惰性求值

C#语言小部分采用了非严格求值策略,大部分仍是严格求值策略。

非严格求值的例子:逻辑或

static void NonStrictEvaluation()

        {

            bool ret = true || DoSomeThing() > 0;

            Console.WriteLine("Done!");

        }

严格求值策略:首先定义一个返回Int的方法

        static int DoSomeThing()

        {

            Console.WriteLine("DoSomeThing Function Excuted");

            return 7;

        }

static void StrictEvaluation(bool flag, int dsVal)

        {

            if (flag)

                Console.WriteLine("dsVal result value is {0}", dsVal);

            Console.WriteLine("Done!");

        }

调用:StrictEvaluation(false, DoSomeThing());

输出:

DoSomeThing Function Excuted

Done!

虽然flag为false,可是DoSomeThing仍是被执行了,如何改变?

将第二个参数改为方法:

static void LazyEvaluation(bool flag,Func<int> dsthing)

        {

            if (flag)

                Console.WriteLine("dsthing result value is {0}", dsthing());

            Console.WriteLine("Done!");

        }

调用:StrictEvaluation(false, DoSomeThing);

 

若是flag为true,而且其中调用两次,那么DoSomeThing就会被执行两次。再次修改

static void LazyEvaluationEx(bool flag, Func<int> dsthing)

        {

            Lazy<int> lzDshting = new Lazy<int>(dsthing);

            if (flag)

      Console.WriteLine("dsthing square result value is {0}", lzDshting.Value * lzDshting.Value);

            Console.WriteLine("Done!");

        }

参考 http://www.cnblogs.com/yaozhenfa/category/652982.html

相关文章
相关标签/搜索