您的当前位置:首页正文

纯粹之美——浅谈纯函数式语言Haskell

来源:爱够旅游网
纯粹之美 ■文,韩祝鹏 浅谈纯函数式语言Haskell 纯函数,给定一个特定输入只能有一个特定的输 程 HaskelI小史 HaskeII是一个标准的学院派的语言,一 直在学术圈子里面被研究。话说70年代经历了 Fo rtran、Lisp、Prolog、C等语言“生物大爆 炸”之后,学术界开始更加关注函数式语言,这 方面的理论要往前追溯到Lambda演算和Haskell CU rry的逻辑理论等。1 977年图灵奖得奖演说 中,约翰.巴克斯的题目就是《程序设计能从冯.诺 依曼体系中解放出来吗?程序的函数式风格及其 代数》。这之后在实验室里出现了大批试验性质 的语言,到了80年代,这些学术界的人觉得各 自为战太分散力量,于是决定合力搞一个语言, 这就是Haskell的诞生。Haskell的名字就来源 于Haskell Curry这个人。 Curry这个词也是函数式编 程中重要的概念。 在象牙塔里面悠哉游 哉了2O来年之后,外面的 Haskel I语言LOGO 世界已经翻天覆地了,计 算机史上的20年,差不多 就像是唐宋演进到明清一般啊。而直到此时, 行业中的开发者才慢慢接受并有意识地引入函 数式编程概念。谈完掌故,再谈谈语言本身。 Haskell的主要特点就是:纯函数式、强类型、 惰性计算,以及对于函数组合和高阶函数的完美 支持。 纯函数式与类型系统 所谓纯函数式,就是指一个函数不能有副作 用,即不能有lO操作、全局变量操作。对于一个 出,无论调用多少次,无论从哪个CPU上调用, 也无论在调用它之前还调用过别的什么,总之只 要输入确定了,输出就确定了。 纯函数的特性有助于程序正确性的验证,方 便测试,也更容易预测一个程序的行为。另外, 这种特性天生就是为多核和并行做好了准备啊! 现在流行的Map/Reduce算法框架,就是把数据拆 分成多块扔到多个CPU上去计算(Map操作), 然后再合并在一起(Reduce操作),这个概念本 身即来自函数式语言。如果通过函数式语言直接 编写计算程序,只需要由底层语言或者类库提供 支持,程序不需大的改动即可实现并行计算。 但是大家可能开始迷惑了,一个纯函数, 不能进行lO操作,那它怎么读文件?怎么输 出“Hello world”?怎么访问数据库?别急, Haskell里面用了Monad的概念,帮助干这些“脏 活”。Haskell是一门通用语言,可以实现所有的 读写文件、访问数据库等功能。但是它把lO操作 与纯函数严格的隔离开。怎么办到呢?Haskell 的另一个特性:强类型的特性就发挥作用了。 Haskell里面每一个函数都有一个类型签名,整 个程序就像是很多个形状不同的拼图,两个函数 必须形状匹配才能拼装在一起。任何带有副作用 的函数,其类型签名中都会带上一个IO的类型 标签,这个IO类型标签就像一个戳,只要带上 了,任何调用它的函数也必须有fO标签。只有 带有lO类型标签的函数才能调用另一个带有lO 标签的函数,而在IO函数内可以调用纯函数,而 反过来,纯函数却不可以调用带有IO标签的函 数。绕晕了吗?“Hello world”登场吧: fact::Int一>Int fact 0=1 fact n=n fact(n一1) main::10() main print(fact 5》 阶乘函数就是函数式语言里面的HelJo world。这个fact函数的类型是Int一>Int,即输入 lnt类型参数,输出lnt类型结果。在这个函数里 作者简介: 韩祝鹏,从事Android应 用产品与运营,曾从事 金山“云安全”服务端 架构开发及运营。喜欢 各种不同范式的编程语 言,尤其钟情于Haskell与 SmalItalk,工作中主要使 用Python ̄,l:lShelf处理海量 数据。博客地址:http:// blog csdn net/alberL Iee 2O10 O8 83 面不能进行任何的IO操作。看到Int一>lnt这个类型,我们就 能很放心地说:这个函数不会私下写文件、连数据库或修改 全局变量。我们可以很放心地把它随意移动.位置,或多次调 用,而不必担心出现莫名其妙的错误。 而main函数是Haskell的程序入口点,它的类型是fO 一个zip函数(Python中同样存在,思路相通)。 zip的函数类型为: zip::[a]一>[b]一>【(a,b)】 ≈臻 非常直观,不过这里的a、b是类型变量,不要搞混。 Haskell代码: ist! st2 (),它里面调用的print函数,类型为: print::(Show a):>a一>IO() hello -”world ・ functional I _ 蠢 .谚; 0 囊 瓢 4 3 3 1、 programmingI、、 在这里我们暂且不管Show a这些,只要注意到它带有 1s ; istl ist2 。  l一个IO()的部分,说明这个函数进行了lo操作,有副作用。 main函数因为本身是}o类型的,因此可以调用print ̄数。但 是在fact函数中却不能调用print函数。 Haskell的求值方式是惰性计算,即把所有的计算都推迟 到其结果真的需要时。惰性不只是推迟下计算的事:它深刻地 影响了我们写程序的方式。我们可以写出下面这样的代码: l lnumberl8 。毒f1..) 它表示1,2,3,4,5,….,是一个无限列表。不用担心内 存放不下。因为需要多少,才生成多少: >take 10 numbers [ 2,3,4,5,6,7,8,9,10] 不过要小心,如果print numbers,那就要陷入无限的输 出中了,Ctrl—C停止吧。 程序例子 用一门纯函数式语言编程,需要适应函数式的思路。可能 大家会说了: “FP程序实在看不懂啊。”其实,很多时候FP的 程序是对问题最好的描述方式。还是通过一个例子来说明吧。 两个列表,一个列表中的元素是字符串,一个列表中的 元素是数字, 1stI _f.he1lo”,“world”,”functiona1“, ”programming”] 1st2=[4,3,3,7]  ̄Elstl列表中的字符串用Ist2 ̄0表中的数字来截取,获得 新的列表为: [“hell”, “wor”, “fun”, “Program”] 我们先用过程式的思路来解决它,很简单,就是一个for 循环嘛。用Python来实现: lSt3 [】 for i in range(4): s lst1【i] length一1st2 fi】 lSt3.append(s[:length]) 这个程序结构我们时常可以看到,对两个列表中对应 的位置合并处理。这很像一条拉链,两列数据对应咬合在一 起。我们有没有想过,可以把这些常见的“模式”提取成一 个通用的语言构件呢? 我们从函数式的思路再考虑这个问题。在Haskell中有 84 程序员 Istz的值为:【("hello",4),(”world",3),(”functional”,3), (”programming”,7)】。 新的列表中每个元素为(String,Int)类型,我们定义一 个处理它的函数,函数类型为:(String,Int)一>String。 ffun《un::(S,1)Stri ng,Itake l s …一>String 《 譬黪 ≯ l 0 执行结果为: >mapfunIstz —【-'hell","wo 'fun","program’1 我们把程序串起来: ist map tun k ip et 、3t 2、 鼍l-t 把两个列表先zip合并,然后再对每一个元素执行一个 操作,这又是一个很常见的操作模式,能不能再提取出一个 语言的固定构件呢?我们可以把函数当作一个参数,定义这 样一个函数: ipAndMa ’。“ … 一zipAndMap f 11 2 ; map f p n、、2>f b _>[。c|J 。萎 j 解释下这个函数的类型定义,其中的((a,b)一>C)正好与 前面fun函数的类型相匹配,这里的a,b,c是类型变量, 用来实现范型函数的声明。 执行它: )ZipAndMap fun jstl Ist2 [’the ,"WO r”,lf ’,lpr。graITIl'] _ 0 llli 其实Haskell中内置了zipWith函数,其类型为: zipwith_.(a一 b~ e)一 [a]一 [b]一>fc】 要使用它我们需要把fun函数的定义稍微修改为: fun2::string一>In[一>stnng 曩 _ fun2 s take s|毫 。 执行: >zipWith fun2 lst1 lSt2 麓。 hell”,”wor”,”fun”,”program”] 毫譬。 我们还可以把fun2的定义也去掉,直接使用lambda匿 名函数: )zipWith r\5 i一)take I s,Istl ist2窖≯一 (.Thell”,”wor”,”fun”,”program”] 一 至此我们得到了这个问题的一个较为简洁的实现,它把 一个特定问题用一个通用的模式来解决。看到这样的程序, 熟悉FP思路的人一眼就可以看出其功能,而且我们有理由相 信,它不会有Bug。因 ̄zipWith以及take函数都是经过严 格检验的,它们拼装在一起的行为是可以放心的。 像这样的zipWith函数,以另一个函数作为参数,即称 为高阶函数。高阶函数是我们在编程中进行抽象简化的重要 手段。我们把编程中时常出现的模式,用高阶函数来表达, 不断地将语言表达能力变得更加通用,然后程序员将高阶函 数组合起来,可以更加自然地表达问题。而整个组装的程 序,建立在强大的类型基础上,既保证了程序的灵活表达能 现在提示符变成了 Main>,这表示我们现在在Main 模块下。我们可以直接使用这个模块下的值和函数: Main>lst1 【t.hello”,”world”,”functional”,”programming”】 力,又保证了组装的正确。类型系统就如同是山间小路的围 栏,在我们不断提升抽象表达能力的高度时,类型系统保证 了我们不会掉落山涧。 4 使用一些列表操作函数的例子: Main>1ength 1st1 Main>map 1ength 1stl [5,5,10,11] 快速上手 Haskell的官方网站:http://www.haskel1.org。这里是 Main>take 2 1st1 [f.helio”,”world”】 调用程序中的main函数 Main>main he11”,”wor ,”fun”, Haskell语言的大本营,可以找到一切有关Haskell的内容:新 闻,文档,开发环境等。Haskell主要有两种开发环境:GHC 和HUGS。它们都是跨平台的,可以运行在MacOSX、Linux l o 船H e ,a.州 1 或者Windows上。其中HUGS是一个轻量的解释环境,程序很 U f i m n e U r l [tthel1” ”wor”,”fun”, [t|hell”,”wor” ”fun”, 下面我们将程序编译成可执行文件,使用ghc,在命令 ¥ghc csdnsample—hs’o csdn8ample —一d n o> 轻巧,但不能编译,适合于教学。另外一种GHC(Glasgow 行下执行: e l七d Haskell Compiler)在实际中应用更加广泛,它可以编译生成 C p e :> l 0 r S m e 8.1 e 0 C p t l 本地代码,并支持并行计算,还提供了很多实用的调优与调试 S./csdnsample —【’'hel1”,”wor”,”fun”,”program”] [.-he11”,”wor”, fun”,’'program”】 [.’hel1”,”wor”,”fun”,”program”】 工具。下面我们就以GHC为例介绍下Haskell的使用。 d l d a n—n.1 ,d e a g d : GHC有三个主要的组件构成: p a m M M (1)ghc,编译器 e n i . n h( . 8 ¥ l i a p p p r r r r (2)ghci,交互式解释器。它提供了一个类1%tPython式的 a C 0 o 0 g g g r r a a d 交互环境。使用者可以在其中导入各种模块,进行测试与执行。 n ¥ m m m (3)runghc,脚本运行器。可以将Haskela l程序当作脚 m e 本直接运行,不需要编译。 要安装GHC,可以到Haskell的官方网站,下载Haskell S p Platform。网址:http://hackage.haskel1.org/platform/。它 区分不同的平台。对于MacOSX,Windows来说,直接下载 相应平台的安装包安装即可。对于Linux用户,可以自己编 译或者下载二进制包。如果是Ubuntu或Debian用户,也可 以直接用apt命令直接安装。 安装好GHC后,我们就可以尝试运行下ghci: 【hanzhupeng@localhost一】¥ghci GHci,version 6.10.4:http://www.haskel1.orgIghcl :?for help Loading package ghc—prim...1inking...done. 代码和运行图:Emacs中编辑Haskell代码的实际状态.里面有些特殊的 符号是Unicode显示的。这是HaskeH ̄;31Ernacs的一个特色功能。 学习使用Haskell是一个不断探索和收获的过程,希望 本文能成为您深入学习函数式编程的起点。 本文浅显地介绍了Haskell的历史和特性,并用一个简 单的例子对比了过程式与函数式编程的思路区别。Haskell 是一门强大的语言,学习它绝对值得。虽然在实际应用中, Haskell还有很多的路要走,但是它所体现出的思想,一定会 Loading package integer…linking…done. Loading package base...1inking...done. Prelude> 在Prelude>提示符后,我们可以直接输入命令执行: Prelude>print”Heiio worid” ”Hello world” 我们可以把刚才编写的程序调入环境,使用:I命令载入 深刻的影响你解决问题的思路。不能影晌一个人思想的语言 程序: 不是好语言,Haskell很好,很强大。0 2O1O 08 85  ●■●■●■●●■■■■■■■■●■■●

因篇幅问题不能全部显示,请点此查看更多更全内容