函数式编程语言Haskell入门(一)——Haskell编程资料
上一篇 / 下一篇 2006-05-29 09:45:00 / 个人分类:程序设计
-函数式编程语言Haskell入门(一)——Haskell编程资料
www.tuenhai.com 20060529
Haskell是什么?
唐宗汉说:
Haskell“比 C++ 更快,比 Perl 更简洁,比 Python 更正规,比 Ruby 更灵活,比 C# 更规范,比 Java 更健壮,和 PHP 则毫无关系”。
函数式编程语言Haskell入门之网络学习资源
http://www.haskell.org/ Haskell主页
http://haskell.org/hawiki/ Haskell wiki
http://www.haskell.org/haskellwiki/Implementations Haskell工具下载
http://www-users.cs.york.ac.uk/~ndm/projects/winhugs.php Win下解释器下载
http://www.haskell.org/visualhaskell/ Win下解释器,编译器。要装VS2003
中文教程:
http://rufi.yculblog.com/post.58940.html rufi的Haskell教程
http://wiki.perlchina.org/main/print/Yet_Another_Haskell_Tutorial flw翻译的教程
http://www.acsu.buffalo.edu/~xluo2/haskell/ 另一个Haskell中文教程
有关文章:
http://svn.perl.org/perl6/pugs/trunk/docs/zh-cn/01Overview.html Haskell与Perl6
http://wiki.perlchina.org/main/show/Interview_with_Autrijus_Tang_perl_com Perl 国际化和 Haskell—采访唐宗汉
http://blog.csdn.net/xxmpp/archive/2005/08/29/467415.aspx Haskell简介
http://blog.csdn.net/xxmpp/archive/2005/09/19/484648.aspx Haskell语法一
http://liubin.itpub.net/post/325/9755 haskell介绍
http://www.dircity.com/newspub/html/3-2/38169.html 关于Haskell
英文教程:
http://www.haskell.org/tutorial/ A Gentle Introduction to Haskell
http://www.isi.edu/%7Ehdaume/htut/ Yet Another Haskell Tutorial
http://www.haskell.org/~pairwise/intro/intro.html Haskell Tutorial for C Programmers
更多英文教程:
http://haskell.org/haskellwiki/Books_and_tutorials
上面的中文教程,为防止“年久失修”,tuenhai.com已经全文转贴在:
http://groups.google.com/group/tuenhai/
用haskell搜索可以很快找到。
为什么要写<函数式编程语言Haskell入门——Haskell编程资料>
这几天在看haskell的资料,多数是英文的。网上关于haskell的中文编程资料奇少,于是想动手把看过的有关英文资料翻译成中文。
1. 从哲学上来说,学习一种知识而没有与外界发生交互,相当于没有学习。
2. 顺便翻译,也不花费非常多的时间。
3. 看英文编程资料是比较郁闷的事情,因为要翻译,大脑就多了一个兴奋点,不太会睡着了。
4. 因为要翻译,人先天的思维惰性就会被克服一些。
不过,要说明的是,本人正处在英语学习阶段,翻译不一定准确,仅供参考(错误处请指正)
tuenhai.com的书橱里有不少直译的外国文学作品,拿起书就头大。本文自然以意译为主,文中代码在Windows XP调试通过(言外之意,没有调试通过的代码就略去了)。
Hugs 零起点
本节内容选自WinHugs Version 20051031的帮助。
安装好WinHugs后,从开始菜单启动,会出现下面的界面:
|| || || || || || ||__ Hugs 98: Based on the Haskell 98 standard
||___|| ||__|| ||__|| __|| Copyright (c) 1994-2005
||---|| ___|| World Wide Web: http://haskell.org/hugs
|| || Report bugs to: mailto:hugs-bugs@haskell.org
|| || Version: 20051031 _______________________________________________
Haskell 98 mode: Restart with command line option -98 to enable extensions
Hugs session for:
file:{Hugs}\libraries\Hugs\Prelude.hs
file:{Hugs}\libraries\Prelude.hs
Type :? for help
Main>
启动WinHugs解释器,标准库{Hugs}\libraries\Hugs\Prelude.hs 会自动载入。如果没有载入,可能是安装有错误。请查看安装说明。
使用Hugs就像使用计算器那么简单。也就是,你会用计算器就会用Hugs。输入表达式,回车,立即计算出结果。比如下面的例子:
Prelude> (2+3)*8
40
Prelude> sum [1..10]
55
Prelude>
开头的 Prelude> 表示Prelude模块已经加载,可以在 Prelude>后面输入表达式,会自动调用Prelude中定义的相应函数执行计算。
第一次,我们输入(2+3)*8,回车,返回值是40.
第二次,我们输入表达式sum [1..10],其中[1..10]表示1至10的list,sum是Prelude中预定义的函数,在这里表示把列表中的所有数相加。在Hugs实际是这样计算的:
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55
如果不嫌烦,也可这样来求值:
Prelude> 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
55
Prelude>
试试:
Prelude> sum [1..10000]
代码是不是非常简洁。如果用其他语言来写,大都要写个for循环来计算。
虽然使用Hugs就象使用计算器那么简单,不过Hugs和大多数计算器不同,Hugs不限制数字的长度。在现代所谓的高级语言中,绝大多数都做不到这点。比如VS 2005, c++等,数字一长就会溢出。从这非常小的一点就可以看出,所谓的高级语言其实都是过时了的,因为程序员的时间比机器的时间更宝贵,现在机器的配置已经不是问题,为什么不让程序员写更少的代码,而让机器来做更多的事情呢。原来tuenhai.com在VS2005中专门写了一个函数来实现大数计算,在Hugs中就用不着这么做了。
Hugs的表达式还可以包含很多的数据类型,比如:numbers,booleans,characters,strings,lists,functions,还有user-defined datatypes.
请看下面的例子:
Prelude> (not True) || False
False
Prelude> reverse "Hugs is cool"
"looc si sguH"
Prelude> filter even [1..10]
[2, 4, 6, 8, 10]
Prelude> take 10 fibs where fibs = 0:1:zipWith (+) fibs (tail fibs)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Prelude>
你不能在命令行里创建函数。要创建并使用函数,要写在文件里并在命令行里载入文件。
上面最后一个例子,我们定义的fib只是在当前表达式有效,并不能在后面的代码里引用它。还有,命令行的表达式只能是单行模式。
Hugs允许把整个程序作为值一样进行计算。例如,putStr "hello, " 是个很简单的程序,在屏幕上打印出"hello, ". 组合使用一个打印出字符串"world"的程序,可以这样:
Prelude> putStr "hello, " >> putStr "world"
hello, world
Prelude>
就象执行标准数学运行一样,类似操作也可以在程序之间进行。例如上面的例子。
在Hugs命令行键入表达式能立即返回结果。有两个特别的命令我们要记住:
:q 退出解释器。
:? 列出所有命令。这在你忘记某个命令的写法时非常有用。
在Hugs里,命令一般都用冒号:开头。
注意,终止键(一般是control-C 或者 control-Break)会终止编译或计算,打印出{Interrupted!},返回命令行等待输入。
在前面的例子中,我们已经使用过prelude中定义的函数,如sum,>>,take。模块是函数的集合,我们可以把包含自定义函数的模块写到文件中。请看下面的例子:
module Fact where
fact :: Integer -> Integer
fact n = product [1..n]
第一行:定义一个名为Fact的模块
第二行:双冒号之前的是函数名,在本例中是fact。双冒号之后是参数列表,用->来连接,第一个Integer表示一个Integer类型参数。最后一个Integer表示返回Integer的值,
第三行:函数的定义部分,n是参数,等号之后则是函数的逻辑部分。
我们把上面的代码保存到Fact.hs(保存路径下面节会讲到)。(在Hugs中约定,保存模块的文件名后缀用.hs,文件名通常用模块名) product函数是prelude中预定义的函数,用以计算数列中各数的乘积,用法和sum函数差不多。在数学中,n的阶乘通常写作n!,也就是fact n = n!
n! = 1 * 2 * ... * (n-1) * n
在数学中阶乘的定义:从1到所给数字中的所有正数相乘。
前面我们定义了一个函数,在使用这个函数之前,要把Fact.hs载入解释器。简单的方法就是使用:load命令。
Prelude> :load Fact
Reading file "Fact.hs":
Hugs session for:
/Hugs/lib/Prelude.hs
Fact.hs
Fact>
在WinHugs中,可以用菜单File,File Manager中载入新的模块。命令提示符变成Fact>表示已经成功载入。
注意,Hugs session for:中列出了已经载入的文件名,第一个总是标准库prelude。 Fact>表示已经载入Fact模块,可以用其中的函数进行计算:
Fact> fact 6
720
Fact> fact 6 + fact 7
5760
Fact> fact 7 `div` fact 6
7
Fact>
另一个例子:
Fact> comb 5 2 where comb n r = fact n `div` (fact r * fact (n-r))
10
Fact>
在上面这个例子中,我们在表达式中定义了comb,如果我们要在以后的代码中用它,就要把这个函数写到文件中。把下面定义加到Fact.hs文件中
comb n r = fact n `div` (fact r * fact (n-r))
重新加载Fact.hs,然后我们就可以随时使用comb函数:
Fact> :reload
Reading file "fact.hs":
Hugs session for:
/Hugs/lib/Prelude.hs
Fact.hs
Fact> comb 5 2
10
Fact>
象多数语言的入门教程一样,我们来写一个Hello World:
Prelude> putStr "Hello World"
Hello World
Prelude>
是不是很简单?如果你要通过外部可执行文件来运行上面的代码,你就要象下面这样把代码写到hello.hs文件中:
module Main where
main = putStr "Hello World"
编译器编译可执行文件一定需要定义一个名叫main的函数。如果只是在解释器中调试程序,函数名可以是其他名字。
开始使用Hugs
因tuenhai.com只有Windows环境,故Unix相关内容略去。
在WinHugs中用+或-符号来开关解释器环境参数。可以在下面的注册表项中自定义:
HKEY_CURRENT_USER\Software\Haskell\Hugs\Winhugs20051031
20051031是Winhugs的版本号。有个Option键,可以在值里设置主要参数。我的键值是:
-s -t -g -l -. +q -Q +w -k -o -O +u -I -T +A +R -H +98 -h917504 -p"%s> " -r"$$" -P".;{Hugs}\\libraries;{Hugs}\\libraries\\Hugs;{Hugs}\\my\\" -S".hs;.lhs" -E"&C:\\WINDOWS\\notepad.exe" -c40
上面注册表值中,-P后面是搜索目录设置,-E后面设置编辑器,默认是notepad.exe。
在安装WinHugs时已经初始化设置了主要的解释器环境参数。
我们也可以在WinHugs菜单File,Options中更改。tuenhai.com只更改了两处:
一. Compile time,Loading Files中加入自己的路径,把下面字符加在系统默认路径后面:
;{Hugs}\my\
上面;是多个搜索路径的分隔符,{Hugs}是WinHugs的安装目录,my是我新建的目录。
我在WinHugs的安装目录下新建了一个my文件夹,自己写的程序都放到这个文件夹里。同时要把这个文件夹的路径加入WinHugs的搜索路径中去,这样在命令行中引用任何自己的文件,都只要写文件名,而不用写路径。就象上节,:load Fact就可以加载Fact文件。
二. 在Hugs Options的WinHugs条,我把默认外部编辑器改成了EmEditor。
Hugs命令行的使用
Hugs能用命令行载入文件,检查或修改参数。几乎所有命令都用冒号:开头,可以用首字母缩写的方式使用常用命令。比如可用:l,:s,:q分别代替:load,:set,:quit命令。
多数Hugs命令带有参数,用空格分开。字符串常量可以包含空格,换行或者其他特殊字符。举例:
:load My File
上面的命令载入了两个文件,My和File。
下面的命令则只载入一个文件:
:load "My File"
:load "My\SPFile"
:load "My\ \ File"
:load My" "File
一般,我们不在文件名包含空格和特殊字符,也不包含引号。
tuenhai.com注:在WinHugs中载入文件,不能加文件扩展名,否则会失败:
Hugs> :load Fact.hs
Reading file "Fact.hs":
ERROR - Unable to open file "Fact.hs"
去掉扩展名:
Hugs> :l Fact
Reading file "D:\Program Files\WinHugs\my\\Fact.hs":
Hugs session for:
file:{Hugs}\libraries\Hugs\Prelude.hs
file:{Hugs}\libraries\Prelude.hs
file:{Hugs}\libraries\Hugs.hs
file:{Hugs}\my\\Fact.hs
Fact>
用putStr打印出字符串:
Prelude> putStr "Hello, world"
Hello, world
Prelude>
用++连接字符串:
Prelude> "Hello" ++ ", " ++ "world"
"Hello, world"
Prelude>
解释器不会执行含有语法错误,类型错误,或引用一个未定义变量的表达式:
Prelude> sum [1..)
ERROR: Syntax error in expression (unexpected `)')
Prelude> sum 'a'
ERROR: Type error in application
*** expression : sum 'a'
*** term : 'a'
*** type : Char
*** does not match : [a]
Prelude> sum [1..n]
ERROR: Undefined variable "n"
Prelude>
一个比较容易出错的地方是:没有应用于特定表达式的show函数,也就是没有该类型的Show类的实例。例如我们在模块里定义了一个类型T:
module Test where
data T = A | B
把这个模块保存到my文件夹Test.hs.然后用:l Test命令加载,命令提示符变成Test>表示已经加载成功。下面我们来测试一下:
Test> A
ERROR: Cannot find "show" function for:
*** expression : A
*** of type : T
Test>
要消除上面的错误,就要添加一个Show类的派生实例。最简单的方法是用下面的方法定义模块:
module Test where
data T = A | B deriving Show
然后Hugs就能计算和显示类型T的值:
Test> A
A
Test> take 5 (cycle [A,B])
[A, B, A, B, A]
Test>
要注意的是,我们可以用:set命令改变解释器的环境参数。
如果我们不带参数使用:set命令,就列出当前的所有参数设置。下面是tuenhai.com的设置:
Test> :s
TOGGLES: groups begin with +/- to turn options on/off resp.
s Print no. reductions/cells after eval
t Print type after evaluation
g Print no. cells recovered after gc
l Literate modules as default
. Print dots to show progress
q Print nothing to show progress
Q Qualify names when printing
w Always show which modules are loaded
k Show kind errors in full
u Use "show" to display results
I Display results of IO programs
T Apply 'defaulting' when printing types
A Auto load files
R Enable root optimisation
OTHER OPTIONS: (leading + or - makes no difference)
hnum Set heap size (cannot be changed within Hugs)
pstr Set prompt string to str
rstr Set repeat last expression string to str
Pstr Set search path for modules to str
Sstr Set list of source file suffixes to str
Estr Use editor setting given by str
cnum Set constraint cutoff limit
Current settings: +wkuAR -stgl.qQIT -h917504 -p"%s> " -r$$ -c40
Search path : -P.;{Hugs}\libraries;{Hugs}\libraries\Hugs;{Hugs}\my\
Source suffixes : -S.hs;.lhs
Editor setting : -E&E:\soft\study\EmEditor\EmEditor.exe
Compatibility : Haskell 98 (+98)
在Windows环境,执行:set命令修改参数后,会将变化记录到注册表相应键值(参见本文前面部分)。
我们也可以在Hugs中使用系统命令行,方法就是使用转义符! 比如在WinHugs中敲入:!dir 就是列出当前目录内容。多数时候,我们可以使用exit命令离开系统命令行返回Hugs。
可以用:?命令列出所有Hugs的命令:
Hugs> :?
LIST OF COMMANDS: Any command may be abbreviated to :c where
c is the first character in the full name.
:load
:load clear all files except prelude
:also
:reload repeat last load command
:edit
:edit edit last module
:module
:type
:? display this list of commands
:set
:set help on command line options
:names [pat] list names currently in scope
:info
:browse
:main
:find
:cd dir change directory
:gc force garbage collection
:version print Hugs version
:quit exit Hugs interpreter
:module
Hugs> :m Prelude
Prelude>
:m是:module命令的缩写形式。命令提示符变成Prelude>表示切换当前模块成功。如果不指定模块名,就切换到最近载入的模块。要注意的是,只能在已经载入的模块内切换。通常,已经载入的模块显示在命令行中。
:cd dir命令用来改变当前工作目录到dir。如果不指定路径,将忽略该命令。
:gc命令可以强制垃圾回收,并打印出已回收的字位数目:
Prelude> :gc
Garbage collection recovered 95766 cells
Prelude>
:quit命令用来退出Hugs。
:load [filename ...]命令清除先前加载的模块,然后尝试从列出的文件中加载定义(definitions),如果某个文件有错误就暂停加载并显示错误信息,修正错误后会启动:reload命令。
在某些系统,在使用:edit命令后会自动执行:reload命令。(在Windowns系统中,解释器和编辑器在执行独立进程时例外)
如果不指定文件名,:load命令清除先前加载的definitions,只保留prelude中提供的definitions.
:load命令会到指定路径搜索用户输入的模块文件,首先搜索精确匹配的文件,如果找不到,系统会尝试加上.hs和.lhs后缀进行搜索。如果你输入:load Array,如果在工作目录下没有名为Array,Array.hs,Array.lhs的文件,系统就会从标准库中加载Array。
:also [filename ...]命令用来附加文件。和:load不同的是,:also不清除先前加载的模块。
(不过,在上一次Reading file以后你又修改了先前的模块,在执行:also前会重新加载修改过的文件)
举例:你先前加载了下面的文件:
Hugs session for:
file:{Hugs}\libraries\Hugs\Prelude.hs
file:{Hugs}\libraries\Prelude.hs
file:{Hugs}\libraries\Hugs.hs
file:{Hugs}\my\\Fact.hs
原来加载了四个文件,你要再加载Test.hs。如果用:l Test命令来加载的话就变成这样:
Hugs session for:
file:{Hugs}\libraries\Hugs\Prelude.hs
file:{Hugs}\libraries\Prelude.hs
file:{Hugs}\libraries\Hugs.hs
file:{Hugs}\my\\Test.hs
可见,:l命令清除了除默认加载的文件以外的文件。
原来加载囝四个文件,如果用:also Test来再加载Test.hs就变成:
Hugs session for:
file:{Hugs}\libraries\Hugs\Prelude.hs
file:{Hugs}\libraries\Prelude.hs
file:{Hugs}\libraries\Hugs.hs
file:{Hugs}\my\\Fact.hs
file:{Hugs}\my\\Test.hs
可见,:also不清除先前加载的所有文件。
如果成功加载,下面命令顺序是等价的:
:load
:also f1
.
.
:also fn
:reload重复最近一次:load命令。如果上次载入文件后,没有任何文件被修改,:reload将不起作用。
:project [project file]加载工程。
:edit [file]启动编辑器编辑文件。
:names [pattern ...]用来搜索所有已载入编译器的函数名称。如果不带任何参数,按字母顺序列出所有名称。
:names参数能接受1个或多个参数,命令行将打印出1个或多匹配的结果。例如:
Prelude> :n fold*
foldl foldl' foldl1 foldr foldr1
(5 names listed)
Prelude>
:names命令可以用通配符,*匹配任意字符,?匹配单个字符,\c精确匹配c。也可以指定匹配范围,如[a-zA-Z]匹配所有字母:
Prelude> :n *map* *[Ff]ile ?
$ % * + - . / : < > appendFile map mapM mapM_ readFile writeFile ^
(17 names listed)
Prelude>
:type expr命令用来不执行计算而打印出表达式的类型:
Prelude> :t "tuenhai.com"
"tuenhai.com" :: String
Prelude> :t putStr "tuenhai.com"
putStr "tuenhai.com" :: IO ()
Prelude> :t sum [1..10]
sum (enumFromTo 1 10) :: (Num a, Enum a) => a
Prelude>
注意,使用:set +t命令设置参数后,Hugs能自动显示多数表达式的类型:
Prelude> :set +t
Prelude> sum [1..10]
55 :: Int
Prelude>
关于WinHugs
WinHugs比较适合Haskell初学者练习使用。在WinHugs中某些库可能不适合使用。因此,大的工程不推荐使用WinHugs。
Hugs 98的标准库有:Array, Char, Complex, IO, Ix, List, Locale, Maybe, Monad, Numeric, Prelude, Random, Ratio, and System,在WinHugs中默认加载Prelude库。我们在学习Haskell要用到其他库的函数,这时可以在默认加载文件中加入import语句加载其他库。
在WinHugs中,默认加载的是下面的文件:
Hugs session for:
file:{Hugs}\libraries\Hugs\Prelude.hs
file:{Hugs}\libraries\Prelude.hs
file:{Hugs}\libraries\Hugs.hs
Prelude.hs是一个标准库,Hugs.hs中包含了main函数。试一下下面的命令:
Hugs> map Char.toUpper "Hello World"
ERROR - Undefined qualified variable "Char.toUpper"
Hugs>
因为没有加载Char库,程序出错。我们在Hugs.hs中用import语句加载Char库。点击WinHugs中列出的
file:{Hugs}\libraries\Hugs.hs
会自动调用默认编辑器打开Hugs.hs文件,文件内容修改为:
module Main where
import Char
main = putStr "Hello World"
然后在WinHugs中用:r命令重新加载文件:
Hugs> :r
Reading file "D:\Program Files\WinHugs\libraries\Hugs.hs":
Reading file "D:\Program Files\WinHugs\libraries\Char.hs":
Reading file "D:\Program Files\WinHugs\libraries\Data\Char.hs":
Reading file "D:\Program Files\WinHugs\libraries\Hugs\Char.hs":
Reading file "D:\Program Files\WinHugs\libraries\Data\Char.hs":
Reading file "D:\Program Files\WinHugs\libraries\Char.hs":
Reading file "D:\Program Files\WinHugs\libraries\Hugs.hs":
Hugs session for:
file:{Hugs}\libraries\Hugs\Prelude.hs
file:{Hugs}\libraries\Prelude.hs
file:{Hugs}\libraries\Hugs\Char.hs
file:{Hugs}\libraries\Data\Char.hs
file:{Hugs}\libraries\Char.hs
file:{Hugs}\libraries\Hugs.hs
从上可见,Char库已经加载。这时我们再用Char库里的函数就不会出错了:
Main> map Char.toUpper "Hello World"
"HELLO WORLD"
《函数式编程语言Haskell入门(一)——Haskell编程资料》就到这里。以上也是tuenhai.com的学习过程的记录,我是从零开始的,想必本教程很适合从零开始自学Haskell的初学者。本文续篇请关注tuenhai.com。
(更多文章请访问www.tuenhai.com 2006529)
标题搜索
日历
|
|||||||||
| 日 | 一 | 二 | 三 | 四 | 五 | 六 | |||
| 1 | 2 | 3 | 4 | ||||||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 | |||
| 12 | 13 | 14 | 15 | 16 | 17 | 18 | |||
| 19 | 20 | 21 | 22 | 23 | 24 | 25 | |||
| 26 | 27 | 28 | 29 | 30 | |||||
我的存档
数据统计
- 访问量: 11274
- 日志数: 107
- 建立时间: 2005-11-30
- 更新时间: 2009-01-05
