1 概要
Haskell 是函数式(一切通过函数调用来完成)、静态、隐式类型(由编译器检测,类型声明不是必需的)、惰性(除非必要,否则什么也不做)的语言。其最大众化的近亲大概是 ML 族语言(不过不是惰性的)。
最流行(common)的 Haskell 编译器是 ,。GHC 在 ,
, , 以及 平台上都有可供使用的二进制包。安装 GHC,即获得 ghc 和 。前者用于将 Haskell 程序库或应用程序编译成二进制码。后者为解释器,可在编写 Haskell 代码后立即得到反馈.
2 表达式
大部份数学表达式都可以输入 ghci 直接解答。Prelude> 是 GHCi 默认提示符。
Prelude>
15
Prelude>
15
Prelude>
16
字符串需要双引号引用,以
连接。
Prelude>
"Hello"
Prelude>
"Hello, Haskell"
调用函数时,参数紧接即可,其间无须添加括号。
Prelude>
6
Prelude>
6
Prelude>
7
Prelude>
1.4142135623730951
Prelude>
True
Prelude>
7
3 控制台
调用 进行控制台输入和输出。如:
Prelude>
putStrLn "Hello, Haskell"
Hello, Haskell
Prelude>
No newlinePrelude>
9
Prelude>
True
和
输出字符串到终端。
输出任意类型的值。(如果
作用于字符串,输出将用引号引用。)
复杂的 I/O acttions 需要
语句块,以分号间隔。
Prelude>
do { putStr "2 + 2 = " ; print (2 + 2) }
2 + 2 = 4
Prelude>
do { putStrLn "ABCDE" ; putStrLn "12345" }
ABCDE
12345
通过
(返回字符串)或
(返回任意你需要的类型)获得输入。用
符号给 I/O action 的结果命名。
Prelude>
do { n <- readLn ; print (n^2) }
4
16
(4 是输入。16 是结果。)
语句块的另一种方式,以缩进取代花括号和分号。虽然在 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( 上是 Test.exe)。顺便接触了 if 语句。
do 之后首个非空白字符,如上例
的 p,是特别的。每新起一行,行首与之对齐,即视为同一 do 块之新句;缩进较之多则继续前句;较之少则结束此 do
块。是为页面布局(layout),Haskell 以之回避语句结束标记和花括号。(故then 和 else 子句务必缩进,否则将脱离 if
语句,导致错误。)
(注意:切勿使用制表符。从技术上讲,八格制表符可以正常工作,但不是个好主意。也不要使用非等宽字体——显然,有些人在编程的时候会犯此糊涂!)
4 类型
到目前为止,我们一直没有提到过类型声明。那是因为 Haskell 暗中推断,不必声明之。如果非要声明类型,可用
符号明确指出,如:
Prelude>
5
Prelude>
5.0
类型 (以及类型类 type ,稍后提及)总是以大写开头。变量(variables)总是以小写开头。这是语言规则,而不只是。
你也可以让 ghci 告诉你选择的内容的类型,这种方法很有用,因为类型声明并不是必需的。
Prelude> :t
Prelude> :t
Prelude> :t
"Hello, Haskell" :: [Char]
(在这个例子中,
是
的另外一种表达方式。参见后面的 。)
有关数字的例子则更加有趣:
Prelude> :t
Prelude> :t
42.0 :: (Fractional t) => t
Prelude> :t
gcd 15 20 :: (Integral t) => t
这些类型用到了 "类型类(type classes)" 含义如下:
- 可作为任意数字(numeric)类型。(这就是为什么我既可以把声明为类型,也可以声明为类型的原因。)
- 可作为任意分数(fractional)类型,但不能是整数(integral)类型。
- (此为函数调用) 可作为任意整数(integral)类型,但不能是分数类型。
在Haskell "Prelude"(你不需要import任何东西就能使用的那部分库)中有五种数字(numeric)类型:
- 是一个至少30位(bit)精度的整数。
- 是一个无限精度的整数。
- 是一个单精度浮点数。
- 是一个双精度浮点数。
- 是一个没有舍入误差的分数/小数类型。
上面5个都是
类型的
实例(instances)。其中前两个是
类型的
实例,后面三种是
类型的
实例。
总的一块来看一下,
Prelude>
7
Prelude>
:1:0:
No instance for (Integral Double)
最后值得一提的类型是,念做"unit"。
它只有一个取值,也写作并念做"unit"。
Prelude>
Prelude> :t
你可以把它看作类似C语言中的void关键字。在一个I/O动作中,如果你不想返回任何东西,你可以返回。
5 有结构的数据
基本数据类型可以很容易的通过两种方式组合在一起:通过 [方括号] 组合的列表(lists),和通过 (圆括号) 组合的元组(tuples)。
列表可以用来储存多个相同类型的值:
Prelude>
[1,2,3]
Prelude>
[1,2,3,4,5]
Prelude>
[1,3,5,7,9]
Prelude>
[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)
Prelude> zip [1 .. 5] ['a' .. 'e']
[(1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')]
上面这个例子用到了 函数,它可以把两个列表组合成一个元组的列表。
类型一般都符合你的期望:
Prelude> :t
Prelude> :t [('x', True), ('y', False)]
[('x', True), ('y', False)] :: [(Char, Bool)]
列表在Haskell中被大量使用。有些函数能够很好地对列表进行运算:
Prelude>
Prelude>
Prelude>
有两个作用于双元素元组的优美函数:
Prelude>
Prelude>
Prelude> map fst [(1, 2), (3, 4), (5, 6)]
详见
6 definitions
We wrote a definition of an earlier, called :
main = do putStrLn "What is 2 + 2?"
x <- readLn
if x == 4
then putStrLn "You're right!"
else putStrLn "You're wrong!"
Now, let's supplement it by actully writing a definition and call it . I'm also adding a module header, which is good form.
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!"
Build again with ghc --make Test.hs. And,
$ ./Test
What is 5! ?
120
You're right!
There's a function. Just like the built-in functions, it can be called as without needing parentheses.
Now ask ghci for the .
$ ghci Test.hs
<< GHCi banner >>
Ok, modules loaded: Main.
Prelude Main> :t
factorial :: (Num a) => a -> a
Function types are written with the argument type, then , then the result type. (This also has the type class .)
Factorial can be simplified by writing it with case analysis.
factorial 0 = 1
factorial n = n * factorial (n - 1)
7 Convenient syntax
A couple extra pieces of are helpful.
secsToWeeks secs = let perMinute = 60
perHour = 60 * perMinute
perDay = 24 * perHour
perWeek = 7 * perday
in secs / perWeek
The
expression defines temporary names. (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"
The expression does a multi-way branch. The special label means "anything else".
8 Using libraries
Everything used so far in this tutorial is part of the , 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 that do the things you need. Documentation on the standard libraries is at . 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 says to use code from and that it will be prefixed by .
(That's necessary because some of the functions have the same names as
functions from the prelude. Most libraries don't need the part.)
If you want something that's not in the standard library, try looking at or this wiki's
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.
9 Topics that don't fit in 10 minute limit
-
-
- Advanced functions
-
-
- Reading files
- Writing Files
阅读(1218) | 评论(0) | 转发(0) |