Difference between revisions of "Cn/十分钟学会 Haskell"

From HaskellWiki
Jump to navigation Jump to search
 
(30 intermediate revisions by 7 users not shown)
Line 1: Line 1:
 
== 概要 ==
 
== 概要 ==
   
 
Haskell 是函数式(一切通过函数调用来完成)、静态、隐式类型([[type|类型]]编译器检测类型声明不是必需的)、惰性(除非必要,否则什么也不做)的语言。其最大众化的近亲大概是 ML 族语言(不过不是惰性的)
Haskell is a functional (that is, everything is done with function calls), statically, implicitly typed ([[type]]s are checked by the compiler, but you don't have to declare them), lazy (nothing is done until it needs to be) language. Its closest popular relative is probably the ML family of languages.
 
   
  +
最流行(common)的 Haskell 编译器是 [[GHC]],[http://www.haskell.org/ghc/download_ghc_661.html 下载地址]。GHC 在 [[GNU/Linux]],
Haskell 是函数式,静态严格类型,惰性语言(一切通过函数调用来完成[[type|类型]]检测是编译器的事无须声明除非必要,什么也不做)。其流行近亲可能是 ML 族语言。
 
 
[[BSD | FreeBSD]], [[Mac OS X |MacOS]], [[Windows]] 以及 [[Solaris]] 平台上都有可供使用的二进制包。安装 GHC,即获得 <tt>ghc</tt> 和 <tt>[[GHC/GHCi | ghci]]</tt>。前者用于将 Haskell 程序库或应用程序编译成二进制码。后者解释器,可在写 Haskell 代码立即得到反馈.
   
 
== 表达式 ==
The most common Haskell compiler is [[GHC]]. You can download GHC from http://www.haskell.org/ghc/download_ghc_661.html . GHC binaries are available for [[GNU/Linux]], [[BSD | FreeBSD]], [[Mac OS X |MacOS]], [[Windows]], and [[Solaris]]. Once you've installed [[GHC]], you get two programs you're interested in right now: <tt>ghc</tt>, and <tt>[[GHC/GHCi | ghci]]</tt>. The first compiles Haskell libraries or applications to binary code. The second is an interpreter that lets you write Haskell code and get feedback right away.
 
   
 
部份数学表达式可以输入 <tt>ghci</tt> 直接解答。<tt>Prelude></tt> 是 GHCi 默认提示符。
最流行的 Haskell 编译器是 [[GHC]],下载地址http://www.haskell.org/ghc/download_ghc_661.html 。GHC 在 [[GNU/Linux]], [[BSD | FreeBSD]][[Mac OS X |MacOS]] [[Windows]]以及 [[Solaris]] 平台上都有可供使用的二进制包。一旦安装 GHC,即获得 <tt>ghc</tt> 和 <tt>[[GHC/GHCi | ghci]]<tt/>。前者用于将 Haskell 程序库或应用程序编译成二进制码。后者解释器,可在写 Haskell 代码立即得到反馈
 
 
== 简单的表达式 ==
 
 
You can type most math expressions directly into <tt>ghci</tt> and get an answer. <tt>Prelude></tt> is the default GHCi prompt.
 
 
多数数学表达式可以直接输入 <tt>ghci</tt> 并得到。<tt>Prelude</tt> 是 GHCi 默认提示符。
 
   
 
Prelude> <hask>3 * 5</hask>
 
Prelude> <hask>3 * 5</hask>
Line 22: Line 17:
 
16
 
16
   
 
字符串需要双引号引用, <hask>++</hask> 连接。
Strings are in "double quotes." You can concatenate them with <hask>++</hask>.
 
 
字符串双引号引用, <hask>++</hask> 连接。
 
   
 
Prelude> <hask>"Hello"</hask>
 
Prelude> <hask>"Hello"</hask>
Line 30: Line 23:
 
Prelude> <hask>"Hello" ++ ", Haskell"</hask>
 
Prelude> <hask>"Hello" ++ ", Haskell"</hask>
 
"Hello, Haskell"
 
"Hello, Haskell"
 
Calling [[function]]s is done by putting the arguments directly after the function. There are no parentheses as part of the function call:
 
   
 
调用函数时,参数紧接[[function|函数]]即可,其间无须添加括号。
 
调用函数时,参数紧接[[function|函数]]即可,其间无须添加括号。
Line 50: Line 41:
 
== 控制台 ==
 
== 控制台 ==
   
[[Introduction to IO |I/O actions]] can be used to read from and write to the console. Some common ones include:
+
调用 [[Introduction to IO |I/O actions]] 进行控制台输入和输出。如:
 
[[Introduction to IO |I/O actions]]用作从控制台输入和输出需要用到 [[Introduction to IO |I/O actions]]。如:
 
   
 
Prelude> <hask>putStrLn "Hello, Haskell"</hask>
 
Prelude> <hask>putStrLn "Hello, Haskell"</hask>
Line 62: Line 51:
 
True
 
True
   
The <hask>putStr</hask> and <hask>putStrLn</hask> functions output strings to the terminal. The <hask>print</hask> function outputs any type of value. (If you <hask>print</hask> a string, it will have quotes around it.)
+
<hask>putStr</hask> <hask>putStrLn</hask> 输出字符串到终端。<hask>print</hask> 输出任意类型的值。(如果 <hask>print</hask> 作用于字符串,输出将用引号引用。)
   
 
复杂的 I/O acttions 需要 <hask>do</hask> 语句块以分号间隔。
<hask>putStr</hask> 和 <hask>putStrLn</hask> 函数输出字符串到终端。<hask>print</hask> 函数输出任意类型的值。(如果 <hask>print</hask> 字符串,输出会用引号引用之.)
 
 
If you need multiple I/O actions in one expression, you can use a <hask>do</hask> block. Actions are separated by semicolons.
 
 
在一个表达式中包含复杂的 I/O acttions, 需要 <hask>do</hask> 语句块。action 之间以分号间隔。
 
   
 
Prelude> <hask>do { putStr "2 + 2 = " ; print (2 + 2) }</hask>
 
Prelude> <hask>do { putStr "2 + 2 = " ; print (2 + 2) }</hask>
Line 76: Line 61:
 
12345
 
12345
   
Reading can be done with <hask>getLine</hask> (which gives back a <hask>String</hask>) or <hask>readLn</hask> (which gives back whatever type of value you want). The <hask> <- </hask> symbol is used to assign a name to the result of an I/O action.
+
通过 <hask>getLine</hask>(返回字符串)或 <hask>readLn</hask>(返回任意你需要的类型)获得输入。用<hask> <- </hask> 符号给 I/O action 的结果命名。
 
通过 <hask>getLine</hask>(返回字符串)或 <hask>readLn</hask>(返回任意你期望的类型)获得输入。用<hask> <- </hask> 符号给 I/O action 的结果命名。
 
   
 
Prelude> <hask>do { n <- readLn ; print (n^2) }</hask>
 
Prelude> <hask>do { n <- readLn ; print (n^2) }</hask>
 
4
 
4
 
16
 
16
 
(The 4 was input. The 16 was a result.)
 
   
 
(4 是输入。16 是结果。)
 
(4 是输入。16 是结果。)
   
  +
<hask>do</hask> 语句块的另一种方式,以缩进取代花括号和分号。虽然在 <tt>ghci</tt> 中未能获得完美支持,但是可以把它们塞进源文件(如 <tt>Test.hs</tt>)里然后编译:
There is actually another way to write <hask>do</hask> blocks. If you leave off the braces and semicolons, then indentation becomes significant. This doesn't work so well in <tt>ghci</tt>, but try putting the file in a source file (say, <tt>Test.hs</tt>) and build it.
 
   
 
<haskell>
 
<haskell>
Line 98: Line 79:
 
</haskell>
 
</haskell>
   
  +
运行 ghc --make Test.hs,得到 Test([[Windows]] 上是 Test.exe)。顺便接触了 if 语句。
You can build with <tt>ghc --make Test.hs</tt>, and the result will be called <tt>Test</tt>. (On [[Windows]], <tt>Test.exe</tt>) You get an <hask>if</hask> expression as a bonus.
 
   
  +
do 之后首个非空白字符,如上例 <hask>putStrLn</hask> 的 p,是特别的。每新起一行,行首与之对齐,即视为同一 do 块之新句;缩进较之多则继续前句;较之少则结束此 do 块。是为页面布局(layout),Haskell 以之回避语句结束标记和花括号。(故then 和 else 子句务必缩进,否则将脱离 if 语句,导致错误。)
The first non-space character after <hask>do</hask> is special. In this case, it's the <tt>p</tt> from <hask>putStrLn</hask>. Every line that starts in the same column as that <hask>p</hask> is another statement in the <hask>do</hask> block. If you indent more, it's part of the previous statement. If you indent less, it ends the <hask>do</hask> block. This is called "layout", and Haskell uses it to avoid making you put in statement terminators and braces all the time. (The <hask>then</hask> and <hask>else</hask> phrases have to be indented for this reason: if they started in the same column, they'd be separate statements, which is wrong.)
 
   
  +
(注意:切勿使用制表符。从技术上讲,八格制表符可以正常工作,但不是个好主意。也不要使用非等宽字体——显然,有些人在编程的时候会犯此糊涂!)
(Note: Do '''not''' indent with tabs if you're using layout. It technically still works if your tabs are 8 spaces, but it's a bad idea. Also, don't use proportional fonts -- which apparently some people do, even when programming!)
 
   
== Simple types ==
+
== 类型 ==
   
  +
到目前为止,我们一直没有提到过类型声明。那是因为 Haskell 暗中推断,不必声明之。如果非要声明类型,可用 <hask>::</hask> 符号明确指出,如:
So far, not a single [[type]] declaration has been mentioned. That's because Haskell does type inference. You generally don't have to declare types unless you want to. If you do want to declare types, you use <hask>::</hask> to do it.
 
   
 
Prelude> <hask>5 :: Int</hask>
 
Prelude> <hask>5 :: Int</hask>
Line 113: Line 94:
 
5.0
 
5.0
   
  +
类型 [[Type|types]](以及类型类 type [[class]]es,稍后提及)总是以大写开头。变量(variables)总是以小写开头。这是语言规则,而不只是[[Study captials|命名习惯]]。
[[Type]]s (and type [[class]]es, discussed later) always start with upper-case letters in Haskell. Variables always start with lower-case letters. This is a rule of the language, not a [[Studly capitals|naming convention]].
 
   
  +
你也可以让 <tt>ghci</tt> 告诉你选择的内容的类型,这种方法很有用,因为类型声明并不是必需的。
You can also ask <tt>ghci</tt> what type it has chosen for something. This is useful because you don't generally have to declare your types.
 
   
 
Prelude> :t <hask>True</hask>
 
Prelude> :t <hask>True</hask>
Line 124: Line 105:
 
<hask>"Hello, Haskell" :: [Char]</hask>
 
<hask>"Hello, Haskell" :: [Char]</hask>
   
(In case you noticed, <hask>[Char]</hask> is another way of saying <hask>String</hask>. See the [[#Structured data|section on lists]] later.)
+
(在这个例子中,<hask>[Char]</hask> <hask>String</hask> 的另外一种表达方式。参见后面的 [[#Structured data|section on lists]] )
   
  +
有关数字的例子则更加有趣:
Things get more interesting for numbers.
 
   
 
Prelude> :t <hask>42</hask>
 
Prelude> :t <hask>42</hask>
Line 135: Line 116:
 
<hask>gcd 15 20 :: (Integral t) => t</hask>
 
<hask>gcd 15 20 :: (Integral t) => t</hask>
   
  +
这些类型用到了 "类型类(type classes)" 含义如下:
These types use "type classes." They mean:
 
   
* <hask>42</hask> can be used as any numeric type. (This is why I was able to declare <hask>5</hask> as either an <hask>Int</hask> or a <hask>Double</hask> earlier.)
+
* <hask>42</hask> 可作为任意数字(numeric)类型。(这就是为什么我既可以把<hask>5</hask>声明为<hask>Int</hask>类型,也可以声明为<hask>Double</hask>类型的原因。)
* <hask>42.0</hask> can be any fractional type, but not an integral type.
+
* <hask>42.0</hask> 可作为任意分数(fractional)类型,但不能是整数(integral)类型。
* <hask>gcd 15 20</hask> (which is a function call, incidentally) can be any integral type, but not a fractional type.
+
* <hask>gcd 15 20</hask> (此为函数调用) 可作为任意整数(integral)类型,但不能是分数类型。
   
  +
在Haskell "Prelude"(你不需要import任何东西就能使用的那部分库)中有五种数字(numeric)类型:
There are five numeric types in the Haskell "prelude" (the part of the library you get without having to import anything):
 
   
* <hask>Int</hask> is an integer with at least 30 bits of precision.
+
* <hask>Int</hask> 是一个至少30位(bit)精度的整数。
* <hask>Integer</hask> is an integer with unlimited precision.
+
* <hask>Integer</hask> 是一个无限精度的整数。
* <hask>Float</hask> is a single precision floating point number.
+
* <hask>Float</hask> 是一个单精度浮点数。
* <hask>Double</hask> is a double precision floating point number.
+
* <hask>Double</hask> 是一个双精度浮点数。
* <hask>Rational</hask> is a fraction type, with no rounding error.
+
* <hask>Rational</hask> 是一个没有舍入误差的分数/小数类型。
   
All five are '''instances''' of the <hask>Num</hask> type class. The first two are '''instances''' of <hask>Integral</hask>, and the last three are '''instances''' of <hask>Fractional</hask>.
+
上面5个都是<hask>Num</hask>类型的'''实例(instances)'''。其中前两个是<hask>Integral</hask>类型的'''实例''',后面三种是<hask>Fractional</hask>类型的'''实例'''。
   
  +
总的一块来看一下,
Putting it all together,
 
   
 
Prelude> <hask>gcd 42 35 :: Int</hask>
 
Prelude> <hask>gcd 42 35 :: Int</hask>
Line 160: Line 141:
 
No instance for (Integral Double)
 
No instance for (Integral Double)
   
  +
最后值得一提的类型是<hask>()</hask>,念做"unit"。
The final type worth mentioning here is <hask>()</hask>, pronounced "unit." It only has one value, also written as <hask>()</hask> and pronounced "unit."
 
  +
它只有一个取值,也写作<hask>()</hask>并念做"unit"。
   
 
Prelude> <hask>()</hask>
 
Prelude> <hask>()</hask>
Line 167: Line 149:
 
<hask>() :: ()</hask>
 
<hask>() :: ()</hask>
   
  +
你可以把它看作类似C语言中的<tt>void</tt>关键字。在一个I/O动作中,如果你不想返回任何东西,你可以返回<hask>()</hask>。
You can think of this as similar to the <tt>void</tt> keyword in C family languages. You can return <hask>()</hask> from an I/O action if you don't want to return anything.
 
   
== Structured data ==
+
== 有结构的数据 ==
   
  +
基本数据类型可以很容易的通过两种方式组合在一起:通过 [方括号] 组合的'''列表(lists)''',和通过 (圆括号) 组合的'''元组(tuples)'''。
Basic data types can be easily combined in two ways: lists, which go in [square brackets], and tuples, which go in (parentheses).
 
   
  +
列表可以用来储存多个相同类型的值:
Lists are used to hold multiple values of the same type.
 
   
 
Prelude> <hask>[1, 2, 3]</hask>
 
Prelude> <hask>[1, 2, 3]</hask>
Line 184: Line 166:
 
[True,False,True]
 
[True,False,True]
   
  +
Haskell 中的字符串(String)其实只不过是字符(Character)类型的 List:
Strings are just lists of characters.
 
   
 
Prelude> <hask>['H', 'e', 'l', 'l', 'o']</hask>
 
Prelude> <hask>['H', 'e', 'l', 'l', 'o']</hask>
 
"Hello"
 
"Hello"
   
  +
冒号 <hask>:</hask> 运算符用来把一个项(item)添加到列表的开始处。(相当于LISP语言中的<tt>cons</tt>函数)
The <hask>:</hask> operator appends an item to the beginning of a list. (It is Haskell's version of the <tt>cons</tt> function in the Lisp family of languages.)
 
   
 
Prelude> <hask>'C' : ['H', 'e', 'l', 'l', 'o']</hask>
 
Prelude> <hask>'C' : ['H', 'e', 'l', 'l', 'o']</hask>
 
"CHello"
 
"CHello"
   
  +
元组则和列表不同,它用来储存固定个数,但类型不同的值。【译者注:列表是类型相同,但个数不固定,甚至还可以是无限个数】
Tuples hold a fixed number of values, which can have different types.
 
   
 
Prelude> <hask>(1, True)</hask>
 
Prelude> <hask>(1, True)</hask>
Line 201: Line 183:
 
[(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]
 
[(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]
   
  +
上面这个例子用到了 <hask>zip</hask> 函数,它可以把两个列表组合成一个元组的列表。
The last example used <hask>zip</hask>, a library function that turns two lists into a list of tuples.
 
   
  +
类型一般都符合你的期望:
The types are probably what you'd expect.
 
   
 
Prelude> :t <hask>['a' .. 'c']</hask>
 
Prelude> :t <hask>['a' .. 'c']</hask>
Line 210: Line 192:
 
<hask>[('x', True), ('y', False)] :: [(Char, Bool)]</hask>
 
<hask>[('x', True), ('y', False)] :: [(Char, Bool)]</hask>
   
  +
列表在Haskell中被大量使用。有些函数能够很好地对列表进行运算:
Lists are used a lot in Haskell. There are several functions that do nice things with them.
 
   
 
Prelude> <hask>[1 .. 5]</hask>
 
Prelude> <hask>[1 .. 5]</hask>
Line 219: Line 201:
 
<hask>[3,4,5]</hask>
 
<hask>[3,4,5]</hask>
   
  +
有两个作用于双元素元组的优美函数:
There are two nice functions on ordered pairs (tuples of two elements):
 
   
 
Prelude> <hask>fst (1, 2)</hask>
 
Prelude> <hask>fst (1, 2)</hask>
Line 228: Line 210:
 
<hask>[1,3,5]</hask>
 
<hask>[1,3,5]</hask>
   
Also see [[how to work on lists]]
+
详见[[how to work on lists|如何使用列表]]
   
== [[Function]] definitions ==
+
== [[函数]]定义 ==
   
We wrote a definition of an [[Introduction to Haskell IO/Actions |IO action]] earlier, called <hask>main</hask>:
+
我们已经定义了一个名为<hask>main</hask>的[[Introduction to Haskell IO/Actions |IO动作]]函数:
   
 
<haskell>
 
<haskell>
Line 242: Line 224:
 
</haskell>
 
</haskell>
   
  +
现在,我们定义一个名为<hask>factorial</hask>(阶乘)的''[[函数]]''对上面的函数做一点补充。我添加了一个模块头部,这是一种良好的风格。
Now, let's supplement it by actully writing a ''[[function]]'' definition and call it <hask>factorial</hask>. I'm also adding a module header, which is good form.
 
   
 
<haskell>
 
<haskell>
Line 256: Line 238:
 
</haskell>
 
</haskell>
   
Build again with <tt>ghc --make Test.hs</tt>. And,
+
使用命令<tt>ghc --make Test.hs</tt>重新编译。并用下面的命令执行
   
 
$ ./Test
 
$ ./Test
Line 263: Line 245:
 
You're right!
 
You're right!
   
  +
这是一个函数。表现得就和内建函数一样,可以通过<hask>factorial 5</hask>来调用。
There's a function. Just like the built-in functions, it can be called as <hask>factorial 5</hask> without needing parentheses.
 
   
Now ask <tt>ghci</tt> for the [[type]].
+
现在向<tt>ghci</tt>询问函数<hask>factorial</hask>的[[类型]].
   
 
$ ghci Test.hs
 
$ ghci Test.hs
Line 275: Line 257:
 
Function types are written with the argument type, then <hask> -> </hask>, then the result type. (This also has the type class <hask>Num</hask>.)
 
Function types are written with the argument type, then <hask> -> </hask>, then the result type. (This also has the type class <hask>Num</hask>.)
   
  +
<hask>factorial</hask>函数可以通过case得以简化:
Factorial can be simplified by writing it with case analysis.
 
   
 
<haskell>
 
<haskell>
Line 282: Line 264:
 
</haskell>
 
</haskell>
   
== Convenient syntax ==
+
== 语法规则 ==
  +
查看[[:Category:Syntax |语法]]以了解更多有用的语法。
 
A couple extra pieces of [[:Category:Syntax |syntax]] are helpful.
 
   
 
<haskell>
 
<haskell>
Line 291: Line 272:
 
perDay = 24 * perHour
 
perDay = 24 * perHour
 
perWeek = 7 * perday
 
perWeek = 7 * perday
in secs * perWeek
+
in secs / perWeek
 
</haskell>
 
</haskell>
   
The <hask>let</hask> expression defines temporary names. (This is using layout again. You could use {braces}, and separate the names with semicolons, if you prefer.)
+
<hask>let</hask>表达式定义了临时名称。(This is using layout again. You could use {braces}, and separate the names with semicolons, if you prefer.)
   
 
<haskell>
 
<haskell>
Line 303: Line 284:
 
</haskell>
 
</haskell>
   
The <hask>case</hask> expression does a multi-way branch. The special label <hask>_</hask> means "anything else".
+
<hask>case</hask>表达式实现了多路分支。符号<hask>_</hask>表示"任何东西"
   
 
== Using libraries ==
 
== Using libraries ==
Line 366: Line 347:
 
** Writing Files
 
** Writing Files
   
[[Category:Tutorials]]
+
[[Category:Tutorials]] [[Category:Chinese]]
  +
语言: [[Learn Haskell in 10 minutes|English]] [[Cn/十分钟学会 Haskell|简体中文]]

Latest revision as of 07:39, 12 February 2012

概要

Haskell 是函数式(一切通过函数调用来完成)、静态、隐式类型(类型由编译器检测,类型声明不是必需的)、惰性(除非必要,否则什么也不做)的语言。其最大众化的近亲大概是 ML 族语言(不过不是惰性的)。

最流行(common)的 Haskell 编译器是 GHC下载地址。GHC 在 GNU/Linux, FreeBSD, MacOS, Windows 以及 Solaris 平台上都有可供使用的二进制包。安装 GHC,即获得 ghc ghci。前者用于将 Haskell 程序库或应用程序编译成二进制码。后者为解释器,可在编写 Haskell 代码后立即得到反馈.

表达式

大部份数学表达式都可以输入 ghci 直接解答。Prelude> 是 GHCi 默认提示符。

 Prelude> 3 * 5
 15
 Prelude> 4 ^ 2 - 1
 15
 Prelude> (1 - 5)^(3 * 2 - 4)
 16

字符串需要双引号引用,以 ++ 连接。

 Prelude> "Hello"
 "Hello"
 Prelude> "Hello" ++ ", Haskell"
 "Hello, Haskell"

调用函数时,参数紧接函数即可,其间无须添加括号。

 Prelude> succ 5
 6
 Prelude> truncate 6.59
 6
 Prelude> round 6.59
 7
 Prelude> sqrt 2
 1.4142135623730951
 Prelude> not (5 < 3)
 True
 Prelude> gcd 21 14
 7

控制台

调用 I/O actions 进行控制台输入和输出。如:

 Prelude> putStrLn "Hello, Haskell"
 Hello, Haskell
 Prelude> putStr "No newline"
 No newlinePrelude> print (5 + 4)
 9
 Prelude> print (1 < 2)
 True

putStrputStrLn 输出字符串到终端。print 输出任意类型的值。(如果 print 作用于字符串,输出将用引号引用。)

复杂的 I/O acttions 需要 do 语句块,以分号间隔。

 Prelude> do { putStr "2 + 2 = " ; print (2 + 2) }
 2 + 2 = 4
 Prelude> do { putStrLn "ABCDE" ; putStrLn "12345" }
 ABCDE
 12345

通过 getLine(返回字符串)或 readLn(返回任意你需要的类型)获得输入。用<- 符号给 I/O action 的结果命名。

 Prelude> do { n <- readLn ; print (n^2) }
 4
 16

(4 是输入。16 是结果。)

do 语句块的另一种方式,以缩进取代花括号和分号。虽然在 ghci 中未能获得完美支持,但是可以把它们塞进源文件(如 Test.hs)里然后编译:

main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

运行 ghc --make Test.hs,得到 Test(Windows 上是 Test.exe)。顺便接触了 if 语句。

do 之后首个非空白字符,如上例 putStrLn 的 p,是特别的。每新起一行,行首与之对齐,即视为同一 do 块之新句;缩进较之多则继续前句;较之少则结束此 do 块。是为页面布局(layout),Haskell 以之回避语句结束标记和花括号。(故then 和 else 子句务必缩进,否则将脱离 if 语句,导致错误。)

(注意:切勿使用制表符。从技术上讲,八格制表符可以正常工作,但不是个好主意。也不要使用非等宽字体——显然,有些人在编程的时候会犯此糊涂!)

类型

到目前为止,我们一直没有提到过类型声明。那是因为 Haskell 暗中推断,不必声明之。如果非要声明类型,可用 :: 符号明确指出,如:

 Prelude> 5 :: Int
 5
 Prelude> 5 :: Double
 5.0

类型 types(以及类型类 type classes,稍后提及)总是以大写开头。变量(variables)总是以小写开头。这是语言规则,而不只是命名习惯

你也可以让 ghci 告诉你选择的内容的类型,这种方法很有用,因为类型声明并不是必需的。

 Prelude> :t True
 True :: Bool
 Prelude> :t 'X'
 'X' :: Char
 Prelude> :t "Hello, Haskell"
 "Hello, Haskell" :: [Char]

(在这个例子中,[Char]String 的另外一种表达方式。参见后面的 section on lists 。)

有关数字的例子则更加有趣:

 Prelude> :t 42
 42 :: (Num t) => t
 Prelude> :t 42.0
 42.0 :: (Fractional t) => t
 Prelude> :t gcd 15 20
 gcd 15 20 :: (Integral t) => t

这些类型用到了 "类型类(type classes)" 含义如下:

  • 42 可作为任意数字(numeric)类型。(这就是为什么我既可以把5声明为Int类型,也可以声明为Double类型的原因。)
  • 42.0 可作为任意分数(fractional)类型,但不能是整数(integral)类型。
  • gcd 15 20 (此为函数调用) 可作为任意整数(integral)类型,但不能是分数类型。

在Haskell "Prelude"(你不需要import任何东西就能使用的那部分库)中有五种数字(numeric)类型:

  • Int 是一个至少30位(bit)精度的整数。
  • Integer 是一个无限精度的整数。
  • Float 是一个单精度浮点数。
  • Double 是一个双精度浮点数。
  • Rational 是一个没有舍入误差的分数/小数类型。

上面5个都是Num类型的实例(instances)。其中前两个是Integral类型的实例,后面三种是Fractional类型的实例

总的一块来看一下,

 Prelude> gcd 42 35 :: Int
 7
 Prelude> gcd 42 35 :: Double
 
 <interactive>:1:0:
     No instance for (Integral Double)

最后值得一提的类型是(),念做"unit"。 它只有一个取值,也写作()并念做"unit"。

 Prelude> ()
 ()
 Prelude> :t ()
 () :: ()

你可以把它看作类似C语言中的void关键字。在一个I/O动作中,如果你不想返回任何东西,你可以返回()

有结构的数据

基本数据类型可以很容易的通过两种方式组合在一起:通过 [方括号] 组合的列表(lists),和通过 (圆括号) 组合的元组(tuples)

列表可以用来储存多个相同类型的值:

 Prelude> [1, 2, 3]
 [1,2,3]
 Prelude> [1 .. 5]
 [1,2,3,4,5]
 Prelude> [1, 3 .. 10]
 [1,3,5,7,9]
 Prelude> [True, False, True]
 [True,False,True]

Haskell 中的字符串(String)其实只不过是字符(Character)类型的 List:

 Prelude> ['H', 'e', 'l', 'l', 'o']
 "Hello"

冒号 : 运算符用来把一个项(item)添加到列表的开始处。(相当于LISP语言中的cons函数)

 Prelude> 'C' : ['H', 'e', 'l', 'l', 'o']
 "CHello"

元组则和列表不同,它用来储存固定个数,但类型不同的值。【译者注:列表是类型相同,但个数不固定,甚至还可以是无限个数】

 Prelude> (1, True)
 (1,True)
 Prelude> zip [1 .. 5] ['a' .. 'e']
 [(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]

上面这个例子用到了 zip 函数,它可以把两个列表组合成一个元组的列表。

类型一般都符合你的期望:

 Prelude> :t ['a' .. 'c']
 ['a' .. 'c'] :: [Char]
 Prelude> :t [('x', True), ('y', False)]
 [('x', True), ('y', False)] :: [(Char, Bool)]

列表在Haskell中被大量使用。有些函数能够很好地对列表进行运算:

 Prelude> [1 .. 5]
 [1,2,3,4,5]
 Prelude> map (+ 2) [1 .. 5]
 [3,4,5,6,7]
 Prelude> filter (> 2) [1 .. 5]
 [3,4,5]

有两个作用于双元素元组的优美函数:

 Prelude> fst (1, 2)
 1
 Prelude> snd (1, 2)
 2
 Prelude> map fst [(1, 2), (3, 4), (5, 6)]
 [1,3,5]

详见如何使用列表

函数定义

我们已经定义了一个名为mainIO动作函数:

main = do putStrLn "What is 2 + 2?"
          x <- readLn
          if x == 4
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

现在,我们定义一个名为factorial(阶乘)的函数对上面的函数做一点补充。我添加了一个模块头部,这是一种良好的风格。

module Main where

factorial n = if n == 0 then 1 else n * factorial (n - 1)

main = do putStrLn "What is 5! ?"
          x <- readLn
          if x == factorial 5
              then putStrLn "You're right!"
              else putStrLn "You're wrong!"

使用命令ghc --make Test.hs重新编译。并用下面的命令执行

 $ ./Test
 What is 5! ?
 120
 You're right!

这是一个函数。表现得就和内建函数一样,可以通过factorial 5来调用。

现在向ghci询问函数factorial类型.

 $ ghci Test.hs
 << GHCi banner >>
 Ok, modules loaded: Main.
 Prelude Main> :t factorial
 factorial :: (Num a) => a -> a

Function types are written with the argument type, then ->, then the result type. (This also has the type class Num.)

factorial函数可以通过case得以简化:

factorial 0 = 1
factorial n = n * factorial (n - 1)

语法规则

查看语法以了解更多有用的语法。

secsToWeeks secs = let perMinute = 60
                       perHour   = 60 * perMinute
                       perDay    = 24 * perHour
                       perWeek   =  7 * perday
                   in  secs / perWeek

let表达式定义了临时名称。(This is using layout again. You could use {braces}, and separate the names with semicolons, if you prefer.)

classify age = case age of 0 -> "newborn"
                           1 -> "infant"
                           2 -> "toddler"
                           _ -> "senior citizen"

case表达式实现了多路分支。符号_表示"任何东西"。

Using libraries

Everything used so far in this tutorial is part of the Prelude, which is the set of Haskell functions that are always there in any program.

The best road from here to becoming a very productive Haskell programmer (aside from practice!) is becoming familiar with other libraries that do the things you need. Documentation on the standard libraries is at http://haskell.org/ghc/docs/latest/html/libraries/. There are modules there with:

module Main where

import qualified Data.Map as M

errorsPerLine = M.fromList
    [ ("Chris", 472), ("Don", 100), ("Simon", -5) ]

main = do putStrLn "Who are you?"
          name <- getLine
          case M.lookup name errorsPerLine of
              Nothing -> putStrLn "I don't know you"
              Just n  -> do putStr "Errors per line: "
                            print n

The import says to use code from Data.Map and that it will be prefixed by M. (That's necessary because some of the functions have the same names as functions from the prelude. Most libraries don't need the as part.)

If you want something that's not in the standard library, try looking at http://hackage.haskell.org/packages/hackage.html or this wiki's applications and libraries page. This is a collection of many different libraries written by a lot of people for Haskell. Once you've got a library, extract it and switch into that directory and do this:

 runhaskell Setup configure
 runhaskell Setup build
 runhaskell Setup install

On a UNIX system, you may need to be root for that last part.

Topics that don't fit in 10 minute limit

语言: English 简体中文