入门
基础与 GHCi
Haskell 是纯函数式语言。GHCi 是交互式 REPL。函数应用不需要括号——succ 5 等同于其他语言中的 succ(5)。字符串只是字符的列表。
-- GHCi interactive environment
-- launch: ghci (quit with :q)
-- simple arithmetic
> 2 + 3
5
-- function application (no parentheses needed)
> succ 5
6
-- define a function
> double x = x * 2
> double 10
20
-- lists
> [1,2,3,4]
[1,2,3,4]
-- strings are lists of chars
> "hello"
"hello"注释
Haskell 用 -- 表示单行注释,用 {- -} 表示多行注释。与多数语言不同,Haskell 的注释可以嵌套,注释掉已含注释的代码时很方便。
-- single line comment
{- multi-line comment
spanning multiple lines -}
{- nested {- comments -} are allowed -}
-- comments are ignored by the compilerHello World
main 是每个 Haskell 程序的入口。putStrLn 输出字符串并换行。用 ghc 编译为原生可执行文件,或用 runhaskell 解释运行。
-- hello.hs
main :: IO ()
main = putStrLn "Hello, World!"
-- run interpreted
-- runhaskell hello.hs
-- or compile to an executable
-- ghc hello.hs && ./helloGHCi 中的 let
在 GHCi 中,let 定义绑定不需要 in。在源文件中,顶层定义不使用 let,而表达式内的 let 需要配对的 in。这一差异常让初学者困惑。
-- in GHCi, 'let' defines a binding (no 'in' needed)
> let x = 5
> let double n = n * 2
> x + double x
15
-- in a source file, top-level bindings need no 'let'
y = 10
-- 'let' inside an expression needs 'in'
z = let a = 2 in a * a -- 4类型查看
:t(或 :type)显示表达式的类型;:: 读作“具有……类型”。:i(info)显示类型类实例与定义。这些 GHCi 命令是探索陌生代码的利器。
-- :t shows the type of an expression
> :t 'a'
'a' :: Char
> :t True
True :: Bool
> :t "hi"
"hi" :: [Char]
> :t (+)
(+) :: Num a => a -> a -> a
-- :info shows typeclass info
> :info Num加载文件
:load 把模块加载进 GHCi;:reload 在编辑后重新加载。:browse 列出模块的所有导出。:main 用命令行参数运行程序。这些让 REPL 工作流很顺滑。
-- GHCi commands
> :load MyModule.hs -- or :l
> :reload -- or :r, reload after edits
> :type main -- check a type
> :browse Data.List -- list a module's exports
> :main arg1 arg2 -- run main with args
-- :set +t auto-prints types of results类型
基本类型
Int 是定长整数;Integer 无界,可按需增长。为精度优先用 Double 而非 Float。Char 存放单个 Unicode 字符。用类型标注让类型更明确。
-- Int: bounded integer (platform-dependent)
x :: Int
x = 9
-- Integer: arbitrary-precision (unbounded)
big :: Integer
big = 2 ^ 100
-- Float / Double (prefer Double)
piVal :: Double
piVal = 3.14159
-- Bool
flag :: Bool
flag = True
-- Char (single Unicode character)
c :: Char
c = 'a'类型标注
:: 为表达式或绑定添加类型标注。类型签名通常位于定义上方。它们提升可读性并让编译器更早发现错误。
-- explicit type signature above a binding
age :: Int
age = 30
-- annotate an expression inline with ::
result = (5 :: Int) + 3
-- function signature
add :: Int -> Int -> Int
add x y = x + y
-- annotate a list
nums :: [Double]
nums = [1.0, 2.0, 3.0]类型变量
类型签名中的小写标识符是类型变量——可代表任意类型,使函数成为多态的。同一个变量在一条签名中必须指代同一类型。
-- 'a' is a type variable (polymorphic)
id' :: a -> a
id' x = x
-- works for any type
> id' 5
5
> id' "hello"
"hello"
-- length works on any list
length :: [a] -> Int函数类型
-> 是右结合的:a -> b -> c 表示 a -> (b -> c)。最后一个类型是返回类型,其余是参数类型。函数可以接受或返回其他函数。
-- '->' is right-associative
add :: Int -> Int -> Int
-- equivalent to: Int -> (Int -> Int)
-- takes a function as an argument
apply :: (a -> b) -> a -> b
apply f x = f x
-- returns a function
const' :: a -> b -> a
const' x y = xMaybe 与 Either
Maybe 表示可能失败的计算(Just 值或 Nothing)。Either 携带错误信息(Left 错误或 Right 值)。两者都替代 null 与异常,实现更安全的错误处理。
-- Maybe: optional values
data Maybe a = Nothing | Just a
safeDiv :: Double -> Double -> Maybe Double
safeDiv _ 0 = Nothing
safeDiv x y = Just (x / y)
-- Either: a value or an error
data Either a b = Left a | Right b
-- convention: Left = error, Right = success
parse :: String -> Either String Int
parse s = Right (read s)元组与列表类型
元组 (a, b) 存放固定数量、可能不同类型的值。列表 [a] 存放任意数量、同一类型的值。元组的类型编码了其大小;列表的类型则不。
-- tuple: (a, b)
pair :: (Int, String)
pair = (1, "one")
-- list: [a]
nums :: [Int]
nums = [1, 2, 3]
-- list of tuples
pairs :: [(Int, String)]
pairs = [(1, "a"), (2, "b")]
-- empty list keeps the polymorphic type
empty :: [a]
empty = []函数
函数定义
函数由名称、参数和 = 定义。应用只需一个空格——无需括号或逗号。add 3 4 实际上是 ((add 3) 4),这是柯里化的结果。
-- name, parameters, '=', body
double :: Int -> Int
double x = x * 2
-- application: just a space, no parentheses
> double 5
10
-- multiple parameters (curried)
add :: Int -> Int -> Int
add x y = x + y
> add 3 4
7函数应用与 $
应用比运算符结合更紧,且是左结合的。$ 是低优先级、右结合的应用运算符,用于省略最右侧参数周围的括号。
-- application is left-associative
> max 3 5
5
> max (max 3 5) 7
7
-- '$' has the lowest precedence, right-associative
> sum (map (*2) [1..5])
30
> sum $ map (*2) [1..5] -- same, fewer parens
-- chain reads left to right
> putStrLn $ show $ sum [1..10]
55运算符作为函数
用括号包裹运算符即可作为函数使用:(+) 2 3。节(section)部分应用运算符:(+10)、(3/)。反引号把二元函数变为中缀:10 `div` 3 更自然。
-- wrap an operator in () to use as a function
> (+) 2 3
5
> (*) 4 5
20
-- sections: partial application of operators
> (+10) 5
15
> (3/) 12
0.25
> (/3) 12
4.0
-- backticks make a function infix
> 10 `div` 3
3
> 10 `mod` 3
1函数组合
. 从右向左组合函数:(f . g) x = f (g x)。它支持无参风格,省略参数。链式调用从内向外读,但避免了层层括号。
-- (.) composes functions right to left
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f (g x)
> negate . sum $ [1,2,3]
-6
-- chaining
fn = ceiling . negate . tan
> fn (pi/4)
-1
-- point-free style
odd' = not . even函数作为值
函数是一等公民:可作为参数传递、被返回、存入数据结构。applyTwice 接受一个函数并应用两次。这是高阶编程的基础 。
-- functions are first-class values
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
> applyTwice (+3) 10
16
> applyTwice (\x -> x*x) 2
16
-- pass functions to higher-order functions
> map (*2) [1..5]
[2,4,6,8,10]
> filter even [1..6]
[2,4,6]中 缀与前缀
任何二元函数都可用反引号写成中缀(3 `elem` xs),任何运算符都可用括号写成前缀(mod 10 3)。按可读性选择即可。
-- infix operators can be written prefix with ()
> mod 10 3
1
> div 10 3
3
-- functions can be written infix with backticks
> 10 `mod` 3
1
> 10 `div` 3
3
-- elem: membership test, often infix
> 3 `elem` [1,2,3]
True列表
列表基础
列表是同类型元素的单链表。字符串就是 [Char]。:(cons)以 O(1) 在头部插入。列表不可变——操作返回新列表。