这是Anders Hejlsberg(不必介绍这是谁了吧)在比利时TechDays 2010所做的开场讲演。由于最近我在博客上关于言语的评论比较多,出于应景,也计划将Anders的讲演完整地听写出来。在上一部分中,Anders论述了他眼中声明式编程的理念及DSL,并演示C#中一种内部DSL的办法:LINQ。在这一部分中,Anders谈及了声明式编程的另一个重要组成部分:函数式编程,并运用.NET平台上的函数式编程言语F#进行了演示。
假如没有特别阐明,一切的文字都直接翻译自Anders的讲演,并运用我自己的白话习气表达出来,关于Anders的口误及重复等状况,必要时在译文中天然也会进行疏忽。为了便利了解,我也会将视频中要害部分进行截图,而某些代码演示则会直接作为文章内容宣布。
(听写开端,接上篇)
关于声明式编程的还有一部分重要的内容,那便是函数式编程。函数式编程现已有很长时刻的前史了,当年LISP便是个函数式编程言语。除了LISP以外咱们还有其他许多函数式编程言语,如APL、Haskell、Scheme、ML等等。关于函数式编程在学术界现已有过许多研讨了,在大约5到10年前许多人开端吸收和收拾这些研讨内容,想要把它们融入更为通用的编程言语。现在的编程言语,如C#、Python、Ruby、Scala等等,它们都受到了函数式编程言语的影响。
我想在这儿先花几分钟时刻简略介绍一下我眼中的函数式编程言语。我发现很多人听说过函数式编程言语,但还不非常清楚它们和一般的指令式编程言语终究有什么差异。现在咱们在运用指令式编程言语写程序时,咱们常常会写这样的句子,嗨,x等于x加一,此刻咱们很多依靠的是状况,可变的状况,或许说变量,它们的值能够随程序运转而改动。
可变状况非常强壮,但随之而来的便是叫做“副效果”的问题。在运用可变状况时,你的程序则会包括副效果,比方你会写一个无需参数的void办法,然后它会依据你的调用次数或是在哪个线程上进行调用对程序发生影响,由于void办法会改动程序内部的状况,然后影响之后的运转效果。
而在函数式编程中则不会呈现这个状况,由于一切的状况都是不可变的。你能够声明一个状况,可是不能改动这个状况。并且由于你无法改动它,所以在函数式编程中不需要变量。事实上对函数式编程的评论更像是数学、公式,而不像是程序句子。假如你把x = x + 1这句话交给一个程序员看,他会说“啊,你在增加x的值”,而假如你把它交给一个数学家看,他会说“嗯,我知道这不是true”。
可是,假如你给他看这条言语,他会说“啊,y等于x加一,便是把x + 1的核算成果交给y,你是为这个核算指定了一个姓名”。这时分在考虑时便是另一种办法了,这儿y不是一个变量,它仅仅x + 1的称号,它不会改动,永久代表了x + 1。
所以在函数式编程言语中,当你写了一个函数,承受一些参数,那么当你调用这个函数时,影响函数调用的仅仅你传进去的参数,而你得到的也仅仅核算成果。在一个纯函数式编程言语中,函数在核算时不会对进行一些奇特的改动,它只会运用你给它的参数,然后回来成果。在函数式编程言语中,一个void办法是没有意义的,它仅有的效果仅仅让你的CPU发热,而不能给你任何东西,也不会有副效果。当然现在你或许会说,这个CPU发多少热也是一个副效果,好吧,不过咱们现在先不评论这个问题。
这儿的要害在于,你解决问题的办法和曾经大不一样了。我这儿仍是用代码来阐明问题。运用函数式言语写没有副效果的代码,就比如在Java或C#中运用final或是readonly的成员。
例如这儿,咱们有一个Point类,结构函数承受x和y,还有一个MoveBy办法,能够把一个点移动一些方位。 在传统的指令式编程中,咱们会改动Point实例的状况,这么做在平常或许不会有什么问题。可是,假如我把一个Point目标一起交给3个API运用,然后我修正了Point,那么怎么才干告知它们状况改动了呢?或许咱们能够运用工作,blablabla,假如咱们没有工作,那么就会呈现那些不愉快的副效果了。
那么运用函数式编程的办法写代码,你的Point类仍是能够包括状况,例如x和y,不过它们是readonly的,一旦初始化今后就不能改动了。MoveBy办法不能改动Point目标,它只能创立一个新的Point目标并回来出来。这便是一个创立新Point目标的函数,不是吗?这样就能够让调用者来决定是运用新的仍是旧的Point目标,但这儿不会有发生副效果的状况呈现。
在函数式编程里天然不会只要Point目标,例如咱们会有调集,如Dictionary,Map,List等等,它们都是不可变的。在函数式编程中,当咱们向一个List里增加元素时,咱们会得到一个新的List,它包括了新增的元素,但之前的List仍然存在。所以这些数据结构的完成办法是有根本性差异的,它们的内部结构会设法让这类操作变的尽或许高效。
在函数式编程中拜访状况是非常安全的,由于状况不会改动,我能够把一个Point或List目标交给恣意多的当地去拜访,彻底不必忧虑副效果。函数式编程的非常简略并行,由于我在运转时不会修正状况,因而不管多少线程在运转时都能够观察到正确的状况。两个函数彻底无关,因而它们是并行仍是次序地履行便没有什么差异了。咱们还能够有推迟核算,能够进行Memorization,这些都是函数式编程中非常风趣的方面。
你或许会说,那么咱们为什么不都用这种办法来写程序呢?嗯,终究,就像我之前说的那样,咱们不能只让CPU发热,咱们有必要要把核算成果表现出来。那么咱们在屏幕上打印内容时,或许把数据写入文件或是Socket时,其实就发生了副效果。因而实在国际中的函数式编程,往往都是把朴实的部分进行阻隔,或是进行更详尽的操控。事实上也不会有真实朴实的函数式编程言语,它们都会带来必定的副效果或是指令式编程的才干。可是,它们默许是函数式的,例如在函数式编程言语中,一切东西默许都是不可变的,你有必要做些额定的工作才干运用可变状况或是发生风险的副效果。此刻你的编程观念便会有所不同了。
咱们在自己的环境中开发出了这样一个函数式编程言语,F#,现已包括在VS 2010中了。F#诞生于微软剑桥研讨院,由Don Syme提出,他在F#上现已工作了5到10年了。F#运用了另一个函数式编程言语OCaml的常见中心部分,因而它是一个强类型言语,并支撑一些如方式匹配,类型揣度等现代函数式编程言语的特性。在此之上,F#又增加了异步工作流,衡量单位等较为前沿的言语功用。
而F#最为重要的一点或许是,在我看来,它是第一个和工业级的结构和东西集,如.NET和Visual Studio,有深化集成的函数式编程言语。F#答应你运用整个.NET结构,它和C#也有相似的履行期特征,例如强类型,并且都会生成高效的代码等等。我想,现在应该是展现一些F#代码的时分了。
首要我想先从F#中我最喜欢的特性讲起,这是个F#指令行……(翻开指令行窗口以及一个F#源文件)……F#包括了一个交互式的指令行,这答应你直接输入代码并履行。例如输入5……x等于5……然后x……显现出x的值是5。然后让sqr x等于x乘以x,所以我这儿界说了一个简略的函数,名为sqr。所以咱们就能够核算sqr 5等于25,sqr 10等于100。
F#的运用办法非常动态,但事实上它是一个强类型的编程言语。咱们再来看看这儿。这儿我界说了一个核算平方和的函数sumSquares,它会遍历每个列表中每个元素,平方后再把它们相加。让我先用指令式的办法编写这个函数,再运用函数式的办法,这样你能够看出其间的差异。
let sumSquaresI l =
let mutable acc = 0
for x in l do
acc <- acc + sqr x
acc
这儿先是指令式的代码,咱们先创立一个累加器acc为0,然后遍历列表l,把平方加到acc中,然后终究我回来acc。有几件工作值得注意,首要为了创立一个可变的状况,我有必要显式地运用mutable进行声明,在默许状况下这是不可变的。
还有一点,这段代码里我没有供给任何的类型信息。当我把鼠标逗留在办法上时,就会显现sumSquaresI办法承受一个int序列作为参数并回来一个int。你或许会想int是哪里来的,嗯,它是由类型揣度而来的。编译器从这儿的0发现acc有必要是一个int,所以它发现这儿的加号表明两个int的相加,所以sqr函数回来的是个int,再接下来blablabla……终究它发现这儿处处都是int。
假如我把这儿修正为浮点数0.0,鼠标再逗留一下,你就会发现这个函数承受和回来的类型都变成float了。所以这儿的类型揣度功用非常强壮,也非常便利。
现在我能够挑选这个函数,让它在指令行里履行,然后调用sumSquaresI,供给1到100的序列,就能得到成果了。
let rec sumSquaresF l =
match l with
| [] -> 0
| h :: t -> sqr h + sumSquaresF t
那么现在咱们来换一种函数式的风格。这儿是另一种写法,能够说是纯函数式的完成办法。假如你去了解这段代码,你会发现有不少数学的感觉。这儿我界说了sumSqauresF函数,输入一个l列表,然后运用下面的方式去匹配l。假如它为空,则成果为0,不然把列表匹配为头部和尾部,然后便将头部的平方和尾部的平方和相加。
你会发现,在核算时我不会去改动任何一个变量的值,我仅仅创立新的值。我这儿会运用递归,就像在数学里咱们常常运用递归,把一个公式分解成几个改变的办法,以此进行递归的界说。在编程时咱们也运用递归的做法,然后编译器会设法帮咱们转化成尾递归或是循环等等。
所以咱们便能够履行sumSquaresF函数,也能够得到相同的成果。当然实际上或许你并不会像之前这样写代码,你或许会运用高阶函数:
let sumSquares l = Seq.sum (Seq.map (fun x -> x * x) l )
例如这儿,我仅仅把函数x乘以x映射到列表上,然后相加。这样也能够得到相同的成果,并且这或许是更典型的做法。我这儿仅仅想阐明,这个言语在编程时或许会给你带来彻底不同的感触,尽管它的履行期特征和C#比较挨近。
这便是关于F#的内容。
(未完待续)