转自 http://blog.zhaojie.me/2010/05/trends-and-future-directions-in-programming-languages-by-anders-3-functional-programming-and-fsharp.html
这是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#的内容。
(未完待续)
分享到:
相关推荐
【编程语言的发展趋势及未来方向】 编程语言的发展历程充满了创新与变革。从早期的Turbo Pascal到现代的C#和VB,以及新兴的F#、Scala和Clojure,编程语言的进步反映了技术的演进和需求的变化。Anders Hejlsberg在...
当前,编程语言的发展趋势主要体现在声明式编程风格、函数式编程和动态语言研究上。声明式编程风格,如领域特定语言(DSL),通过表达“做什么”而不是“怎么做”,来简化问题的表达。函数式编程强调不可变性和函数...
最后,书中还可能包含**编程语言的历史和发展**,以及**新兴编程语言的特点**,帮助读者了解编程语言演进的趋势和未来可能的方向。 通过阅读《编程语言原理(第10版)》,读者不仅可以掌握编程语言的核心概念,还能...
4. **多范式融合**:未来的编程语言可能会融合多种编程范式,如函数式、面向对象和声明式编程,以适应不同场景的需求。 5. **跨平台兼容性**:随着物联网和边缘计算的发展,编程语言需要更好地支持跨设备和跨操作...
例如,函数式编程语言在处理大规模数据和并发操作时展现出优势,而低代码/无代码平台则降低了编程的门槛,让更多非专业人员也能参与软件开发。 总结来说,计算机编程语言的发展伴随着计算机科学的进步,不断优化人...
未来,编程语言的发展趋势可能会朝着以下几个方向发展: 1. 更强的自动化和智能辅助:借助人工智能和机器学习,编程环境可能提供更智能的代码提示和自动完成功能,甚至能自动生成部分代码。 2. 更高的可读性和可...
- 结合不同编程范式的优点,如函数式编程、声明式编程等,以提高代码的可读性和可维护性。 3. **自然语言处理** - 探索如何让编程语言更加贴近自然语言,降低学习门槛,提高编程效率。 4. **智能编程辅助** -...
- **发展趋势**:探讨Perl未来的发展方向及其在新技术领域的应用前景。 - **学习建议**:为初学者提供学习Perl的有效路径和资源推荐。 通过以上内容的学习,读者不仅能够掌握Perl的基础语法,还能深入了解其高级...
- **未来趋势**:考虑编程语言的发展趋势和技术演进方向。 总之,在计算机应用软件开发中,合理选择编程语言对于项目的成功至关重要。通过对不同编程语言的特点和适用场景的深入了解,可以帮助开发团队做出更加明智...
3. **编程范式的演变**:面向对象编程(OOP)、函数式编程(FP)、声明式编程等不同编程范式之间的优劣对比和发展趋势也是重要的讨论点之一。 4. **软件架构的演进**:微服务架构、无服务器架构(Serverless)、容器化技术...
编程语言的未来发展趋势将更加注重易用性、可扩展性和跨平台能力。例如,函数式编程语言的兴起,如Haskell和Scala,强调程序的纯函数性质和不可变数据,以减少副作用和提高并发性能。同时,随着人工智能和大数据的...
每种语言都有其独特的优势,例如Groovy在脚本编写方面具有优势,而Scala在函数式编程和并发处理方面表现出色。这些语言的共存不仅丰富了JVM的生态,也为解决不同领域的特定问题提供了更多的选择。 随着JVM语言的...
4. **对程序员的影响**:对于程序员来说,理解这些趋势至关重要,因为它们将决定编程语言、框架和工具的选择,以及职业发展方向。 5. **行业应对策略**:孟岩或许还会讨论企业如何适应指数式技术变化,包括投资策略...
计算机语言的发展趋势表明,未来的编程语言可能会更加注重可读性、可维护性、安全性和性能。随着人工智能和机器学习的崛起,还可能出现更多专门为这些领域设计的语言或框架。同时,随着物联网和边缘计算的兴起,编程...