Cn/十分钟学会 Haskell

From HaskellWiki
Revision as of 06:20, 19 December 2007 by Wangzhifen (talk | contribs) (控制台)

Jump to: navigation, search


Haskell 是函数式,静态严格类型,惰性语言(一切通过函数调用来完成;类型检测是编译器的事,声明类型不是必需的;除非必要,否则什么也不做,nothing is done until it needs to be)。其流行近亲可能是 ML 族语言。

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


大多数数学表达式只要直接输入 ghci 就可得到答案。Prelude 是 GHCi 默认提示符。

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

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

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


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


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

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

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

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

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

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

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

(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 语句,导致错误。)


Simple types

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 :: to do it.

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

Types (and type classes, 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 naming convention.

You can also ask ghci what type it has chosen for something. This is useful because you don't generally have to declare your types.

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

(In case you noticed, [Char] is another way of saying String. See the section on lists later.)

Things get more interesting for numbers.

 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

These types use "type classes." They mean:

  • 42 can be used as any numeric type. (This is why I was able to declare 5 as either an Int or a Double earlier.)
  • 42.0 can be any fractional type, but not an integral type.
  • gcd 15 20 (which is a function call, incidentally) can be any integral type, but not a fractional type.

There are five numeric types in the Haskell "prelude" (the part of the library you get without having to import anything):

  • Int is an integer with at least 30 bits of precision.
  • Integer is an integer with unlimited precision.
  • Float is a single precision floating point number.
  • Double is a double precision floating point number.
  • Rational is a fraction type, with no rounding error.

All five are instances of the Num type class. The first two are instances of Integral, and the last three are instances of Fractional.

Putting it all together,

 Prelude> gcd 42 35 :: Int
 Prelude> gcd 42 35 :: Double
     No instance for (Integral Double)

The final type worth mentioning here is (), pronounced "unit." It only has one value, also written as () and pronounced "unit."

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

You can think of this as similar to the void keyword in C family languages. You can return () from an I/O action if you don't want to return anything.

Structured data

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> [1, 2, 3]
 Prelude> [1 .. 5]
 Prelude> [1, 3 .. 10]
 Prelude> [True, False, True]

Strings are just lists of characters.

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

The : operator appends an item to the beginning of a list. (It is Haskell's version of the cons function in the Lisp family of languages.)

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

Tuples hold a fixed number of values, which can have different types.

 Prelude> (1, True)
 Prelude> zip [1 .. 5] ['a' .. 'e']

The last example used zip, a library function that turns two lists into a list of tuples.

The types are probably what you'd expect.

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

Lists are used a lot in Haskell. There are several functions that do nice things with them.

 Prelude> [1 .. 5]
 Prelude> map (+ 2) [1 .. 5]
 Prelude> filter (> 2) [1 .. 5]

There are two nice functions on ordered pairs (tuples of two elements):

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

Also see how to work on lists

Function definitions

We wrote a definition of an IO action earlier, called main:

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 function definition and call it factorial. 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! ?
 You're right!

There's a function. Just like the built-in functions, it can be called as factorial 5 without needing parentheses.

Now ask ghci for the type.

 $ 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 can be simplified by writing it with case analysis.

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

Convenient syntax

A couple extra pieces of syntax are helpful.

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

The let 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 case expression does a multi-way branch. The special label _ means "anything else".

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 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 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 简体中文