- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
本文以 learnxinyminutes-docs 仓库中的西班牙语教程 es/haskell.md 为骨架,结合英文原版 haskell.md 深度展开,系统讲解 Haskell 的语法基础、列表与惰性求值、函数定义与高阶技巧、类型系统、控制流、自定义数据类型、类型类、IO 与 Monad 入门,以及 GHCi 交互式环境的完整使用方式。读完本文,你将掌握用纯函数式思维编写可运行 Haskell 代码的全部核心能力,并能在 GHCi 中即时验证每一个示例。
背景说明:learnxinyminutes-docs 是一个"把编程语言教程写成带注释的可执行代码"的开源仓库(见 README.md),同一语言在不同语言目录下均有翻译版本,如 es/haskell.md、zh-cn/haskell.md、de/haskell.md 等。本文所有示例均可在 GHCi 中直接逐行运行验证。
一、注释、原始数据类型与运算符
Haskell 是面向实用场景设计的纯函数式编程语言(purely functional programming language),以 Monad 与强大的类型系统著称。它的注释语法非常直观:
-- 单行注释使用两个连字符 {- 多行注释可以像这样 包裹在一个块中 -}数字与算术运算
Haskell 中数字直接作为表达式使用,算术运算与主流语言一致:
3 -- 3 1 + 1 -- 2 8 - 1 -- 7 10 * 2 -- 20 35 / 5 -- 7.0需要特别注意的是:Haskell 默认的除法/返回浮点数而非整数,这与许多语言中"整数相除得整数"的行为不同:
35 / 4 -- 8.75(浮点除法)若需要整数除法,使用反引号包裹的`div`中缀形式:
35 `div` 4 -- 8(整数除法)布尔值与布尔运算
布尔值True/False是原始数据类型,支持逻辑否定与比较运算:
True False not True -- False not False -- True 1 == 1 -- True 1 /= 1 -- False (注意:不等于用 /= ,而非 !=) 1 < 10 -- True从上面可以看出,not、==、/=等都是函数。Haskell 的函数调用不需要括号,所有参数依次列在函数名之后,通用模式为:
-- func arg1 arg2 arg3 ...字符串与字符
Haskell 严格区分字符串(双引号)与字符(单引号):
"Esto es una cadena." -- 这是一个字符串 'a' -- 这是一个字符 'No puedes utilizar comillas simples para cadenas.' -- 错误!单引号不能用于字符串字符串拼接使用++:
"¡Hola " ++ "mundo!" -- "¡Hola mundo!"字符串本质上是字符的列表,因此字符列表可以直接写为字符串字面量:
['H', 'o', 'l', 'a'] -- "Hola" "Esto es una cadena" !! 0 -- 'E'(取第 0 个字符)关于!!索引运算,英文原版 haskell.md 补充了一个重要事实:列表是链表结构,!!是O(n) 复杂度的操作,与数组语言的 O(1) 随机访问有本质区别。
二、列表、元组与惰性求值
列表基础
列表的所有元素必须是同一类型。下面两种写法完全等价:
[1, 2, 3, 4, 5] [1..5]区间(Range)语法非常灵活,可以作用于字符:
['A'..'F'] -- "ABCDEF"区间支持指定步长(step):
[0,2..10] -- [0, 2, 4, 6, 8, 10] [5..1] -- 不工作!Haskell 默认是递增的 [5,4..1] -- [5, 4, 3, 2, 1](显式给出前两个元素指定步长 -1)列表从 0 开始索引:
[0..] !! 5 -- 5无限列表与惰性求值
Haskell 支持无限列表,这是纯函数式语言的标志性能力:
[1..] -- 所有自然数的列表(无限长)无限列表之所以可行,是因为 Haskell 采用惰性求值(lazy evaluation):Haskell 只在需要时才求值。因此你可以直接取第 1000 个元素:
[1..] !! 999 -- 1000此时 Haskell 只求值了前 1000 个元素,这个"无限"列表的其余部分尚未真正存在——直到你真正需要它们时才被求值。惰性求值是理解 Haskell 一切高级特性的基石,它让"定义无限数据结构"成为日常操作。
列表常用操作
-- 拼接两个列表 [1..5] ++ [6..10] -- [1,2,3,4,5,6,7,8,9,10] -- 在头部添加元素(: 即 cons 操作符) 0:[1..5] -- [0, 1, 2, 3, 4, 5] -- 更多列表函数 head [1..5] -- 1(取首元素) tail [1..5] -- [2, 3, 4, 5](去掉首元素) init [1..5] -- [1, 2, 3, 4](去掉末元素) last [1..5] -- 5(取末元素)列表推导(List Comprehension)
列表推导是 Haskell 中构建列表的声明式语法:
[x*2 | x <- [1..5]] -- [2, 4, 6, 8, 10] [x*2 | x <- [1..5], x*2 > 4] -- [6, 8, 10](带条件过滤)元组(Tuple)
元组与列表的关键区别:元组的元素可以类型不同,但长度固定。
("haskell", 1) -- 二元组示例访问二元组元素使用fst和snd:
fst ("haskell", 1) -- "haskell" snd ("haskell", 1) -- 1英文原版特别提醒:fst/snd仅适用于二元组,对三元组等 n 元组使用会报错。若需提取三元组中间元素,应使用通配符_结合模式匹配(见下一节):
sndOfTriple (_, y, _) = y -- 用 _ 跳过不需要命名的值三、函数定义:从基础到模式匹配
定义与调用
函数定义就是一组方程,左侧是函数名加参数,右侧是表达式:
add a b = a + b注意:如果使用 GHCi 解释器,定义需要加
let前缀:let add a b = a + b(见本文 REPL 章节)。
调用函数直接空格分隔参数:
add 1 2 -- 3中缀调用
用反引号把函数名包起来,即可把普通函数当中缀运算符使用:
1 `add` 2 -- 3自定义运算符
函数名甚至可以不含字母——这让你能定义自己的运算符!下面定义一个整数除法运算符//:
(//) a b = a `div` b 35 // 4 -- 8守卫(Guards)
守卫(guard)是一种简洁的分支机制,用|引导条件,otherwise作为兜底分支:
fib x | x < 2 = 1 | otherwise = fib (x - 1) + fib (x - 2)模式匹配(Pattern Matching)
模式匹配与守卫类似,但按左侧模式进行分派。下面给出fib的三个方程,Haskell 会自动选用第一个与参数值匹配的方程:
fib 1 = 1 fib 2 = 2 fib x = fib (x - 1) + fib (x - 2)元组同样可以模式匹配:
foo (x, y) = (x + 1, y + 2)列表模式匹配是函数式编程的核心范式:x:xs把列表拆成首元素x与剩余部分xs。由此可以手写自己的map:
myMap func [] = [] myMap func (x:xs) = func x:(myMap func xs)匿名函数(Lambda)
匿名函数用反斜杠\加参数列表定义:
myMap (\x -> x + 2) [1..5] -- [3, 4, 5, 6, 7]折叠(Fold)
foldl1表示从左折叠,并把列表第一个值作为累加器初值:
foldl1 (\acc x -> acc + x) [1..5] -- 15(即 1+2+3+4+5)四、高阶函数技巧:部分应用、函数组合与 $
部分应用(Partial Application)
如果只传入部分参数,函数会被"部分应用",返回一个接收剩余参数的新函数:
add a b = a + b foo = add 10 -- foo 现在是"给任意数加 10"的函数 foo 5 -- 15等价写法是运算符节(operator section),直接把二元运算符和一个操作数括起来:
foo = (+10) foo 5 -- 15函数组合
.运算符把函数链在一起:右侧函数的输出作为左侧函数的输入。下面的foo先加 10、再乘 5:
foo = (*5) . (+10) -- (5 + 10) * 5 = 75 foo 5 -- 75$ 运算符与优先级
$是 Haskell 中一个重要的应用运算符。与标准函数应用(优先级最高 10、左结合)不同,$的优先级为 0 且右结合——低优先级意味着其右侧的整个表达式会作为参数传给左侧函数,从而省去大量括号:
-- 之前的写法(需要括号) even (fib 7) -- False -- 等价写法($ 免括号) even $ fib 7 -- False -- 结合函数组合 even . fib $ 7 -- False五、类型签名与强类型系统
Haskell 拥有非常强大的静态类型系统,每个合法表达式都有类型。类型签名用::声明:
5 :: Integer "hola" :: String True :: Bool函数同样有类型。not接收一个 Bool 返回一个 Bool:
-- not :: Bool -> Bool接收两个参数的函数类型用箭头链表示:
-- add :: Integer -> Integer -> Integer良好实践:定义值之前,先在其上方写出类型签名:
double :: Integer -> Integer double x = x * 2类型签名不仅是文档,更是编译器进行静态检查的依据——类型不匹配的代码会在编译期被拒绝。
六、控制流:if 表达式、case 表达式与递归
if 表达式
Haskell 的if是表达式(有值),而非语句。单行写法:
haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"多行写法中缩进至关重要:
haskell = if 1 == 1 then "awesome" else "awful"case 表达式
case用于按值分派,是解析命令行参数的典型手法:
case args of "help" -> printHelp "start" -> startProgram _ -> putStrLn "bad args"通配符_匹配所有未列出的情况。
Haskell 没有循环:用递归与高阶函数替代
Haskell没有循环结构,代之以递归和高阶函数。map对列表每个元素应用函数:
map (*2) [1..5] -- [2, 4, 6, 8, 10]可以用map封装出自己的for:
for array func = map func array -- 使用:\i -> show i 是匿名函数 for [0..5] $ \i -> show i -- 也可以直接传函数(show 本身就是函数) for [0..5] showfilter保留满足条件的元素(英文原版补充):
filter even [1..10] -- [2, 4, 8, 10]foldl 与 foldr:列表归约
foldl/foldr将整个列表归约为一个值,签名模式为foldl <fn> <valor inicial> <lista>(折叠函数、初值、列表):
foldl (\x y -> 2*x + y) 4 [1,2,3] -- 43foldl是左折叠,等价于从左侧开始层层嵌套:
-- 等价展开:(2 * (2 * (2 * 4 + 1) + 2) + 3)foldr是右折叠,从右侧开始:
foldr (\x y -> 2*x + y) 4 [1,2,3] -- 16-- 等价展开:(2 * 1 + (2 * 2 + (2 * 3 + 4)))同一个函数表达式,由于折叠方向不同,结果从 43 变成 16——这正是理解 fold 左/右结合语义的关键示例。
七、自定义数据类型
和类型(Sum Type)与枚举
data声明自定义类型:左侧是类型构造器,右侧用|分隔多个数据构造器,构成和类型/联合类型:
data Color = Rojo | Azul | Verde之后即可在函数中模式匹配使用:
say :: Color -> String say Rojo = "¡Es Rojo!" say Azul = "¡Es Azul!" say Verde = "¡Es Verde!"注意类型构造器用于类型签名,数据构造器用于函数体,二者在使用层面分工明确。
带参数的数据构造器
英文原版进一步展示了携带字段的构造器,例如二维坐标点:
data Point = Point Float Float以及多个带参构造器组合的类型:
data Name = Mononym String | FirstLastName String String | FullName String String String此外还有记录语法(record syntax),字段自动生成访问器函数并支持函数式更新:
data Point2D = CartesianPoint2D { x :: Float, y :: Float } | PolarPoint2D { r :: Float, theta :: Float } myPoint = CartesianPoint2D { x = 7.0, y = 10.0 } xOfMyPoint = x myPoint -- 7.0(自动生成的访问器) myPoint' = myPoint { x = 9.0 } -- 记录更新:x 变为 9.0参数化类型:Maybe
自定义类型可以带类型参数,最著名的例子是Maybe:
data Maybe a = Nothing | Just a以下值都属于Maybe类型:
Just "hello" -- 类型为 `Maybe String` Just 1 -- 类型为 `Maybe Int` Nothing -- 类型为 `Maybe a`(对任意 a 都成立)Maybe正是 Haskell 表达"可能失败/可能缺失"的惯用方式,是安全处理空值的基石。
八、类型类(Typeclasses):Haskell 的多态机制
本节内容来自英文原版 haskell.md 的专题章节,是理解 Haskell 标准库组织方式的关键补充。
类型类是 Haskell 实现多态的一种方式,类似于其他语言的接口(interface):它定义一组必须对该类中所有类型成立的函数。以Eq(可判等类型类)为例:
class Eq a where (==) :: a -> a -> Bool (/=) :: a -> a -> Bool x == y = not (x /= y) x /= y = not (x == y)这个声明要求两个函数(==)和(/=),并声明二者可以互相定义——因此你只需实现其中一个,另一个会由类型类定义自动补全。
使用instance关键字让某个类型成为类型类成员:
instance Eq TrafficLight where Red == Red = True Green == Green = True Yellow == Yellow = True _ == _ = False之后即可对TrafficLight使用(==)与(/=)。
带类型类约束的函数签名用=>表达:
isEqual :: (Eq a) => a -> a -> Bool isEqual x y = x == y注意:x与y必须是同一类型。isEqual Red 2不合法——即便Int和TrafficLight都是Eq的实例,类型类也不允许混用不同类型。
其他常见类型类包括:Ord(可排序,支持>、<=等)、Read(可由字符串解析)、Show(可转为字符串显示)、Num/Real/Integral/Fractional(可做数学运算)、Enum(可枚举步进)、Bounded(有最大最小值)。
对于简单类型,可以用deriving让编译器自动生成Eq、Ord、Read、Show、Enum、Bounded实例,免去手写:
data Point = Point Float Float deriving (Eq, Read, Show)英文原版还指出,type关键字可创建类型同义词(如type String = [Char]、type Weight = Float),同义词不需要构造器;但由于同义词最终归约到相同的基础类型,语义上不合理的混用也能编译通过,使用时应谨慎。
九、Haskell IO 与 Monad 入门
IO 的完整解释离不开 Monad,但掌握足够的入门知识就能立即上手编写真实程序。
main 与 putStrLn
程序启动时执行main,它必须返回IO ()类型的值:
main :: IO () main = putStrLn $ "¡Hola, cielo! " ++ (say Blue) -- putStrLn 的类型是 String -> IO ()interact:字符串到字符串的程序
如果能把程序实现为String -> String的纯函数,IO 会变得非常简单。库函数interact读入全部文本、运行函数、打印输出:
-- interact :: (String -> String) -> IO () countLines :: String -> String countLines = show . length . lines main' = interact countLines这里countLines是纯函数,通过.组合show(数值转字符串)、length(行数)、lines(按行拆分),完美示范了"纯核心 + 薄 IO 壳"的架构风格。
do 记法:串联动作
可以把IO ()类型的值理解为"计算机要执行的一系列动作",类似命令式程序。用do记法串联动作:
sayHello :: IO () sayHello = do putStrLn "¿Cual es tu nombre?" name <- getLine -- 读取一行输入并绑定到 name putStrLn $ "Hola, " ++ name练习:写一个只读取一行的interact版本。
注意:sayHello中的代码永远不会自动执行——唯一被执行的入口是main。要运行sayHello,注释掉上面的main定义并替换为main = sayHello。
getLine 与自定义 IO 动作
getLine的类型是IO String。可以把IO a类型的值理解为"一个执行后会生成a类型值的程序"。用<-把生成的值绑定到名字以便复用,也可以组合自己的IO String动作:
action :: IO String action = do putStrLn "Esta es una linea." input1 <- getLine input2 <- getLine -- do 语句块的类型由其最后一行决定 -- return 不是关键字,只是一个普通函数 return (input1 ++ "\n" ++ input2) -- return :: String -> IO String之后action可以像getLine一样使用:
main'' = do putStrLn "¡Volveré a repetir dos líneas!" result <- action putStrLn result putStrLn "Esto es todo, ¡amigos!"IO 即 Monad:纯度与并发
IO是"Monad"的一个实例。Haskell 借助 Monad 做 IO,从而保持纯函数式本质:任何与外部世界交互(做 IO)的函数都会在类型签名中被打上IO标记。这让我们能清晰区分"纯"函数(不交互、不改状态)与不纯的函数。
这是一项强大特性:纯函数易于并发执行,因此 Haskell 的并发编程非常自然——只要函数是纯的,并行化就是安全的,这正是许多高并发服务选择 Haskell 的深层原因。
十、GHCi 交互式环境
GHCi 是 Haskell 的交互式解释器(REPL)。在终端输入ghci启动,即可逐行验证本文所有示例。
定义新值需要使用let:
let foo = 5用:t查看任意值或表达式的类型:
> :t foo foo :: Integer(英文原版还演示了:t +d显示默认类型、:t (:)检查运算符类型、:i获取名字的详细信息、:?列出全部命令等进阶用法,可参见 haskell.md。)
REPL 中可直接运行任何IO ()类型的动作:
> sayHello ¿Cual es tu nombre? Amigo Hola, Amigo十一、综合示例:QuickSort 与安装指引
作为收尾,看一个极能体现 Haskell 声明式风格的经典实现——快速排序。整个算法用三行方程表达,无需任何循环或变量赋值:
qsort [] = [] qsort (p:xs) = qsort lesser ++ [p] ++ qsort greater where lesser = filter (< p) xs greater = filter (>= p) xs把基准p与剩余元素xs分开,用filter分出小于与不小于基准的两部分,递归排序后拼接——可读性与数学定义几乎一一对应。
安装 Haskell:最传统的方式是通过 Haskell Platform(基于 Cabal);较新的方式是 Stack 工具链(Stackage 发行版)。安装完成后即可用ghci进入交互环境,或把代码保存为.hs文件后用runghc/ghc编译运行(仓库中 es/haskell.md 的 frontmatter 指定了示例文件名haskell.hs,英文原版则为learnhaskell.hs)。
进一步学习:本文所有内容都来自仓库中可直接运行的带注释代码。若希望继续深入,可以阅读英文原版 haskell.md 中关于类型类、记录语法与类型同义词的更完整论述,也可以对照 zh-cn/haskell.md 等中文翻译版本交叉学习。经典的进阶读物包括《Learn You a Haskell for Great Good!》《Real World Haskell》以及西班牙语社区的《Aprende Haskell por el bien de todos》——掌握类型类与 Monad 这两大核心概念后,Haskell 的优雅与乐趣将真正向你展开。
- 文档
- 教程
【免费下载链接】learnxinyminutes-docs
Code documentation written as code! How novel and totally my idea!
相关推荐
Bash 入门与实战指南:基于 learnxinyminutes-docs 的 GNU Bash 完整教程
Bash 入门与实战指南:基于 learnxinyminutes docs 的 GNU Bash 完整教程 本指南以 learnxinyminutes docs
文档教程Elm 语言速成指南:learnxinyminutes-docs 中的纯函数式 Web 前端编程入门
Elm 语言速成指南:learnxinyminutes docs 中的纯函数式 Web 前端编程入门 本篇技术指南以 learnxinyminutes docs
文档教程G-Helper终极指南:华硕笔记本轻量级控制工具完全解析
G Helper终极指南:华硕笔记本轻量级控制工具完全解析 G Helper是一款专为华硕笔记本电脑设计的开源控制工具,作为Armoury Crate的轻量级替
桌面应用系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考