diff --git a/scripts/seed_posts.sql b/scripts/seed_posts.sql new file mode 100644 index 0000000..deab74a --- /dev/null +++ b/scripts/seed_posts.sql @@ -0,0 +1,6284 @@ +-- 种子数据:高质量测试文章 +-- 共 17 篇技术文章,覆盖 15+ 种编程语言 + +-- 先创建测试用户(如果不存在) +INSERT INTO users (username, email, password_hash, role) +VALUES ('testuser', 'test@example.com', '$argon2id$v=19$m=65536,t=3,p=4$testtesttesttesttesttesttest$testtesttesttesttesttesttesttesttesttest', 'admin') +ON CONFLICT (username) DO NOTHING; + +-- 插入测试文章 +INSERT INTO posts (author_id, title, slug, summary, content_md, content_html, status, published_at, created_at, updated_at) +VALUES +( + 1, + 'Rust 所有权与生命周期:深入理解内存安全', + 'rust-ownership-lifetime', + '本文深入探讨 Rust 的所有权系统、借用规则和生命周期注解,帮助你理解 Rust 如何在不使用垃圾回收器的情况下保证内存安全。', + $doc$ +# Rust 所有权与生命周期:深入理解内存安全 + +Rust 是一门专注于安全、并发和性能的系统级编程语言。自 2010 年 Mozilla 研究院发起以来,Rust 凭借其独特的内存安全保证机制,逐渐在系统编程领域占据重要地位。与 C/C++ 等语言不同,Rust 不需要依赖垃圾回收器(Garbage Collector),而是在编译期通过**所有权系统**(Ownership System)来确保内存安全。这一设计使得 Rust 程序既拥有接近 C 语言的性能,又避免了手动内存管理带来的种种安全隐患。 + +本文将系统性地介绍 Rust 的所有权、借用和生命周期三大核心概念,帮助你建立对 Rust 内存模型的深入理解。 + +## 为什么需要所有权? + +在传统的系统编程语言中,内存管理通常面临两种选择:手动管理(如 C/C++)或自动垃圾回收(如 Java、Go)。手动管理虽然性能优秀,但容易引发内存泄漏、悬垂指针、双重释放等严重问题;垃圾回收虽然安全,但会带来运行时开销和不可预测的暂停时间。 + +Rust 提出了第三种方案:**在编译期通过所有权规则静态验证内存访问的安全性**。这意味着所有内存安全检查都在编译阶段完成,运行时几乎零开销。 + +## 所有权的三条基本规则 + +Rust 的所有权系统建立在三条简单但强大的规则之上: + +1. **每个值在任意时刻都有且仅有一个所有者(owner)** +2. **当所有者离开作用域时,其拥有的值会被自动释放** +3. **所有权可以通过移动(move)或复制(copy)在不同变量之间转移** + +### 所有权转移示例 + +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = s1; // s1 的所有权转移给 s2 + + // println!("{}", s1); // 编译错误!s1 已无效 + println!("{}", s2); // 正确,s2 拥有该字符串 +} +``` + +在上述代码中,`String::from("hello")` 在堆上分配了一块内存。当执行 `let s2 = s1` 时,Rust 执行的是**移动语义**(move semantics)—— `s1` 将堆内存的所有权转移给 `s2`,之后 `s1` 不再有效。这种设计避免了双重释放问题。 + +### Copy trait 与 Clone trait + +并非所有类型都会执行移动语义。对于栈上分配的简单类型(如整数、浮点数、布尔值等),Rust 会自动实现 `Copy` trait,这意味着赋值时执行的是**按位复制**而非所有权转移: + +```rust +fn main() { + let x = 5; + let y = x; // i32 实现了 Copy,x 仍然有效 + + println!("x = {}, y = {}", x, y); // 完全正确 +} +``` + +对于需要深拷贝的复杂类型,可以使用 `clone()` 方法: + +```rust +fn main() { + let s1 = String::from("hello"); + let s2 = s1.clone(); // 显式深拷贝 + + println!("s1 = {}, s2 = {}", s1, s2); // 两者都有效 +} +``` + +## 借用(Borrowing):安全地共享数据 + +虽然所有权转移解决了内存安全问题,但在实际编程中,我们经常需要临时访问某个值而不获取其所有权。Rust 提供了**借用**机制来解决这个问题。 + +借用分为两种类型:不可变借用和可变借用。 + +### 不可变借用(Immutable Borrowing) + +不可变借用允许你读取数据但不修改它。在同一作用域内,可以创建多个不可变引用: + +```rust +fn calculate_length(s: &String) -> usize { + s.len() +} + +fn main() { + let s = String::from("hello"); + let len = calculate_length(&s); // 传递不可变引用 + + println!("'{}' 的长度是 {}.", s, len); // s 仍然有效 +} +``` + +`&s` 语法创建了一个指向 `s` 的引用,但不会转移所有权。函数参数 `&String` 表示接受一个不可变引用。当函数返回时,引用被销毁,但原始值 `s` 仍然有效。 + +### 可变借用(Mutable Borrowing) + +当你需要修改借用的数据时,可以使用可变引用: + +```rust +fn change(some_string: &mut String) { + some_string.push_str(", world"); +} + +fn main() { + let mut s = String::from("hello"); + change(&mut s); + println!("{}", s); // 输出: hello, world +} +``` + +### 借用规则 + +Rust 的借用检查器强制执行以下规则,这些规则在编译期就能防止数据竞争: + +1. **在任意给定时刻,只能有一个可变引用** 或 **任意数量的不可变引用** +2. **引用必须始终有效**(不能指向已释放的内存) + +```rust +fn main() { + let mut s = String::from("hello"); + + let r1 = &s; // 不可变引用 + let r2 = &s; // 另一个不可变引用(允许) + // let r3 = &mut s; // 编译错误!不可与可变引用共存 + + println!("{} and {}", r1, r2); + + // r1 和 r2 在此之后不再使用 + let r3 = &mut s; // 现在可以创建可变引用 + r3.push_str("!"); + println!("{}", r3); +} +``` + +Rust 编译器使用**非词法生命周期**(Non-Lexical Lifetimes, NLL)来精确追踪引用的使用范围,允许在上面的例子中,当 `r1` 和 `r2` 不再被使用时创建 `r3`。 + +## 生命周期(Lifetime):引用的有效期 + +生命周期是 Rust 编译器用来确保引用始终指向有效数据的机制。它们描述了引用在程序执行期间的有效范围。 + +### 显式生命周期注解 + +当函数返回引用时,编译器需要知道返回的引用与哪个参数的生命周期相关联: + +```rust +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} + +fn main() { + let string1 = String::from("long string is long"); + let result; + { + let string2 = String::from("xyz"); + result = longest(string1.as_str(), string2.as_str()); + println!("最长字符串是 {}", result); // 正确 + } // string2 在此被释放 + // println!("最长字符串是 {}", result); // 编译错误!result 可能引用 string2 +} +``` + +`'a` 是一个生命周期参数,表示 `x`、`y` 和返回值的生命周期至少是 `'a`。编译器确保返回的引用不会比任何一个输入引用活得更久。 + +### 结构体中的生命周期 + +当结构体包含引用字段时,必须为每个引用标注生命周期: + +```rust +struct ImportantExcerpt<'a> { + part: &'a str, +} + +impl<'a> ImportantExcerpt<'a> { + fn level(&self) -> i32 { + 3 + } + + fn announce_and_return_part(&self, announcement: &str) -> &str { + println!("注意:{}", announcement); + self.part + } +} + +fn main() { + let novel = String::from("Call me Ishmael. Some years ago..."); + let first_sentence = novel.split('.').next().expect("找不到 '.'"); + let i = ImportantExcerpt { + part: first_sentence, + }; + // i 不能比 novel 活得更久 +} +``` + +### 生命周期省略规则 + +为了减轻开发者的负担,Rust 编译器在某些情况下可以自动推断生命周期: + +1. **每个引用参数都有自己的生命周期参数** +2. **如果只有一个输入生命周期参数,该生命周期被赋给所有输出生命周期参数** +3. **如果有多个输入生命周期参数,但其中一个是 `&self` 或 `&mut self`,那么 `self` 的生命周期被赋给所有输出生命周期参数** + +```rust +// 以下两个函数签名等价(得益于生命周期省略) +fn first_word(s: &str) -> &str { } +fn first_word<'a>(s: &'a str) -> &'a str { } +``` + +## 'static 生命周期 + +`'static` 是 Rust 中最长的生命周期,它表示引用可以存活于整个程序运行期间。字符串字面量默认具有 `'static` 生命周期: + +```rust +let s: &'static str = "我拥有 'static 生命周期"; +``` + +需要注意的是,`'static` 并不等同于全局变量或永不释放的内存。它只是表示该引用在程序运行期间始终有效。 + +## 生命周期与闭包 + +闭包可以捕获其环境中的变量。Rust 提供了三种捕获方式,对应不同的借用语义: + +```rust +fn make_adder(x: i32) -> impl Fn(i32) -> i32 { + move |y| x + y // move 关键字强制闭包获取所有权 +} + +fn main() { + let add_five = make_adder(5); + println!("{}", add_five(3)); // 输出 8 + println!("{}", add_five(10)); // 输出 15 +} +``` + +使用 `move` 关键字时,闭包会获取捕获变量的所有权,这在需要将闭包返回或传递给其他线程时特别有用。 + +## 常见陷阱与最佳实践 + +### 1. 悬垂引用(Dangling References) + +Rust 编译器会阻止悬垂引用的产生: + +```rust +fn dangle() -> &String { // 编译错误! + let s = String::from("hello"); + &s // s 在函数结束时被释放 +} // 返回的引用指向已释放的内存 +``` + +正确的做法:返回 `String` 本身(转移所有权)或使用 `String` 的切片。 + +### 2. 内部可变性(Interior Mutability) + +有时需要在拥有不可变引用的情况下修改数据。Rust 提供了 `RefCell` 和 `Cell` 等类型来实现**内部可变性**: + +```rust +use std::cell::RefCell; + +fn main() { + let data = RefCell::new(5); + + { + let mut borrow = data.borrow_mut(); + *borrow += 1; + } // 可变借用在此结束 + + println!("{}", data.borrow()); // 输出 6 +} +``` + +### 3. 智能指针与所有权 + +Rust 标准库提供了多种智能指针来辅助内存管理: + +| 智能指针 | 用途 | 所有权模型 | +|----------|------|-----------| +| `Box` | 堆分配 | 独占所有权 | +| `Rc` | 引用计数 | 共享所有权(单线程) | +| `Arc` | 原子引用计数 | 共享所有权(多线程) | +| `RefCell` | 运行时借用检查 | 内部可变性 | + +## 总结 + +Rust 的所有权系统虽然初学者可能会觉得复杂,但一旦掌握,就能编写出既高效又安全的代码。核心理念可以概括为:**编译器通过所有权、借用和生命周期规则,在编译期验证所有内存访问的安全性**。 + +| 概念 | 核心作用 | 使用场景 | +|------|---------|---------| +| 所有权 | 管理值的生命周期 | 所有堆分配的数据 | +| 不可变借用 | 安全地读取数据 | 函数参数、迭代器 | +| 可变借用 | 安全地修改数据 | 状态修改、缓冲区操作 | +| 生命周期 | 验证引用的有效性 | 返回引用、结构体包含引用 | +| Copy trait | 自动按位复制 | 栈上的简单类型 | + +Rust 的设计哲学是:**将内存安全责任从程序员转移到编译器**。虽然这意味着需要花更多时间学习与编译器"沟通",但换来的是零成本抽象的内存安全和无与伦比的运行时性能。随着 Rust 在 Linux 内核、Web 浏览器(Firefox)、云计算(AWS、Azure)等领域的广泛应用,掌握 Rust 的所有权系统已经成为现代系统程序员的核心技能之一。 + +--- + +*本文首发于 Yggdrasil 博客,转载请注明出处。* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '7 days', + NOW() - INTERVAL '7 days', + NOW() - INTERVAL '7 days' +), +( + 1, + 'Python 装饰器完全指南:从入门到精通', + 'python-decorators-guide', + '从基础到高级,全面掌握 Python 装饰器的使用方法,包括函数装饰器、类装饰器、参数化装饰器,以及 functools.wraps、lru_cache 等内置装饰器的实战应用。', + $doc$ +# Python 装饰器完全指南:从入门到精通 + +装饰器(Decorator)是 Python 中最优雅、最强大的特性之一。它本质上是一种**高阶函数**(Higher-Order Function),允许你在不修改原函数源代码的前提下,为函数添加额外的功能。装饰器广泛应用于日志记录、权限验证、缓存、性能监控等场景,是 Python 元编程的核心工具。 + +本文将从基础概念出发,逐步深入到高级用法,帮助你全面掌握 Python 装饰器。 + +## 什么是装饰器? + +在 Python 中,函数是一等公民(First-Class Citizen),这意味着函数可以像普通变量一样被赋值、传递和返回。装饰器正是利用了这一特性: + +> **装饰器是一个接受函数作为参数并返回函数的可调用对象。** + +最简单的装饰器形式如下: + +```python +def my_decorator(func): + def wrapper(): + print("🚀 函数调用前") + func() + print("✅ 函数调用后") + return wrapper + +@my_decorator +def say_hello(): + print("Hello, World!") + +say_hello() +``` + +输出结果: +``` +🚀 函数调用前 +Hello, World! +✅ 函数调用后 +``` + +`@my_decorator` 语法糖等价于 `say_hello = my_decorator(say_hello)`,它将 `say_hello` 函数替换为了 `wrapper` 函数。 + +## 处理带参数的函数 + +上面的装饰器只能装饰无参数的函数。为了让装饰器更通用,需要使用 `*args` 和 `**kwargs`: + +```python +def greeting_decorator(func): + def wrapper(*args, **kwargs): + print(f"🎯 正在调用: {func.__name__}") + result = func(*args, **kwargs) + print(f"✨ {func.__name__} 调用完成") + return result + return wrapper + +@greeting_decorator +def greet(name, greeting="Hello"): + return f"{greeting}, {name}!" + +print(greet("Alice")) +print(greet("Bob", greeting="Hi")) +``` + +输出: +``` +🎯 正在调用: greet +✨ greet 调用完成 +Hello, Alice! +🎯 正在调用: greet +✨ greet 调用完成 +Hi, Bob! +``` + +## 使用 functools.wraps 保留元数据 + +使用装饰器后,原函数的元数据(如函数名、文档字符串)会丢失: + +```python +print(say_hello.__name__) # 输出: wrapper,而不是 say_hello +``` + +为了解决这个问题,Python 标准库提供了 `functools.wraps`: + +```python +import functools + +def my_decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + """wrapper 的文档""" + return func(*args, **kwargs) + return wrapper + +@my_decorator +def say_hello(): + """打招呼函数""" + print("Hello!") + +print(say_hello.__name__) # 输出: say_hello +print(say_hello.__doc__) # 输出: 打招呼函数 +``` + +**最佳实践**:编写装饰器时务必使用 `@functools.wraps(func)`,这是专业 Python 代码的标志。 + +## 参数化装饰器 + +有时装饰器本身需要接收参数。实现参数化装饰器需要嵌套三层函数: + +```python +def repeat(num=2): + """重复执行函数 num 次""" + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + results = [] + for i in range(num): + print(f"第 {i + 1} 次执行...") + result = func(*args, **kwargs) + results.append(result) + return results + return wrapper + return decorator + +@repeat(num=3) +def greet(name): + return f"Hello, {name}!" + +results = greet("Alice") +print(results) +``` + +输出: +``` +第 1 次执行... +第 2 次执行... +第 3 次执行... +['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!'] +``` + +执行流程:`repeat(num=3)` → 返回 `decorator` → `decorator(greet)` → 返回 `wrapper` → `greet` 被替换为 `wrapper`。 + +## 类装饰器 + +除了函数装饰器,Python 还支持**类装饰器**。类装饰器通常用于: + +1. 为类添加新方法或属性 +2. 修改类的行为 +3. 注册类到某个注册表中 + +### 基础类装饰器 + +```python +class CountCalls: + """统计函数被调用的次数""" + + def __init__(self, func): + functools.update_wrapper(self, func) + self.func = func + self.num_calls = 0 + + def __call__(self, *args, **kwargs): + self.num_calls += 1 + print(f"[{self.num_calls}] {self.func.__name__} 被调用") + return self.func(*args, **kwargs) + +@CountCalls +def say_whee(): + print("Whee! 🎉") + +say_whee() +say_whee() +say_whee() +print(f"总共调用了 {say_whee.num_calls} 次") +``` + +输出: +``` +[1] say_whee 被调用 +Whee! 🎉 +[2] say_whee 被调用 +Whee! 🎉 +[3] say_whee 被调用 +Whee! 🎉 +总共调用了 3 次 +``` + +### 使用类实现带状态装饰器 + +类装饰器特别适合需要维护状态的场景: + +```python +class Cache: + """简单的缓存装饰器""" + + def __init__(self, func): + functools.update_wrapper(self, func) + self.func = func + self.cache = {} + + def __call__(self, *args): + if args in self.cache: + print(f"📦 缓存命中: {args}") + return self.cache[args] + + print(f"🔍 计算中: {args}") + result = self.func(*args) + self.cache[args] = result + return result + +@Cache +def fibonacci(n): + if n < 2: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +print(f"fib(5) = {fibonacci(5)}") +print(f"fib(5) = {fibonacci(5)}") # 第二次直接返回缓存 +``` + +## 内置装饰器实战 + +Python 标准库和 functools 模块提供了多个实用的内置装饰器。 + +### @property:将方法变为属性 + +```python +class Circle: + def __init__(self, radius): + self._radius = radius + + @property + def radius(self): + """获取半径""" + return self._radius + + @radius.setter + def radius(self, value): + """设置半径,支持验证""" + if value < 0: + raise ValueError("半径不能为负数") + self._radius = value + + @property + def area(self): + """计算面积""" + return 3.14159 * self._radius ** 2 + +c = Circle(5) +print(f"半径: {c.radius}") +print(f"面积: {c.area:.2f}") +c.radius = 10 +print(f"新面积: {c.area:.2f}") +``` + +### @staticmethod 和 @classmethod + +```python +class DateUtil: + @staticmethod + def is_leap_year(year): + """判断闰年""" + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) + + @classmethod + def from_string(cls, date_str): + """从字符串创建实例""" + year, month, day = map(int, date_str.split('-')) + return cls(year, month, day) + +print(DateUtil.is_leap_year(2024)) # True +``` + +### @functools.lru_cache:自动缓存 + +```python +from functools import lru_cache + +@lru_cache(maxsize=128) +def fibonacci(n): + """带缓存的斐波那契数列""" + if n < 2: + return n + return fibonacci(n - 1) + fibonacci(n - 2) + +# 计算第 100 项 +print(f"fib(100) = {fibonacci(100)}") +print(f"缓存信息: {fibonacci.cache_info()}") +``` + +### @functools.singledispatch:函数重载 + +```python +from functools import singledispatch + +@singledispatch +def process(arg): + """默认实现""" + raise NotImplementedError(f"不支持类型: {type(arg)}") + +@process.register(int) +def _(arg): + return f"整数: {arg * 2}" + +@process.register(str) +def _(arg): + return f"字符串: {arg.upper()}" + +@process.register(list) +def _(arg): + return f"列表: {len(arg)} 个元素" + +print(process(42)) # 整数: 84 +print(process("hello")) # 字符串: HELLO +print(process([1, 2, 3])) # 列表: 3 个元素 +``` + +## 装饰器组合与顺序 + +多个装饰器可以叠加使用,执行顺序为**从下往上**: + +```python +@decorator_a +@decorator_b +def my_func(): + pass + +# 等价于: my_func = decorator_a(decorator_b(my_func)) +``` + +## 实际应用场景 + +| 场景 | 装饰器实现 | 说明 | +|------|-----------|------| +| 日志记录 | `@log_call` | 记录函数调用参数和返回值 | +| 权限验证 | `@require_login` | 检查用户是否已登录 | +| 性能计时 | `@timer` | 测量函数执行时间 | +| 重试机制 | `@retry(max_attempts=3)` | 失败时自动重试 | +| 限流控制 | `@rate_limit(100)` | 限制每秒调用次数 | +| 输入验证 | `@validate_schema` | 验证函数参数格式 | + +## 总结 + +装饰器是 Python 中最具表现力的特性之一,它让代码更加简洁、可复用和符合 DRY 原则。掌握装饰器需要理解: + +1. **函数是一等公民**:函数可以作为参数传递、作为返回值返回 +2. **闭包**:装饰器内部函数可以访问外部函数的变量 +3. **@语法糖**:`@decorator` 等价于 `func = decorator(func)` +4. **functools.wraps**:保留原函数的元数据 +5. **嵌套三层**:实现参数化装饰器需要额外的一层函数 + +> "Python 的装饰器让代码像诗歌一样优美,但过度使用会让代码变得晦涩难懂。" —— 遵循**显式优于隐式**的原则,在合适的场景使用装饰器,避免过度工程化。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '6 days', + NOW() - INTERVAL '6 days', + NOW() - INTERVAL '6 days' +), +( + 1, + 'JavaScript 异步编程完全指南:从回调地狱到 async/await', + 'js-async-programming', + '深入理解 JavaScript 的异步编程模型,从回调函数到 Promise,再到 async/await 的演变历程,以及事件循环的底层原理。', + $doc$ +# JavaScript 异步编程完全指南:从回调地狱到 async/await + +JavaScript 是一门单线程语言,这意味着它一次只能执行一个任务。然而,现代 Web 应用需要处理大量 I/O 操作(如网络请求、文件读写、定时器等),如果采用同步方式处理,整个程序会被阻塞,用户体验极差。为了解决这一问题,JavaScript 采用了**事件驱动、非阻塞 I/O** 的编程模型,并通过**事件循环**(Event Loop)机制实现了异步编程。 + +本文将带你回顾 JavaScript 异步编程的完整演进历程,从最早的回调函数到现代的 async/await,帮助你建立对 JavaScript 并发模型的系统理解。 + +## 回调函数:异步编程的起点 + +在 JavaScript 早期,异步操作主要通过回调函数(Callback)来实现。回调函数是一个被作为参数传递给另一个函数的函数,当异步操作完成时被调用: + +```javascript +function fetchData(callback) { + setTimeout(() => { + callback("数据加载完成"); + }, 1000); +} + +fetchData((data) => { + console.log(data); +}); +``` + +回调函数简单直观,但存在两个主要问题: + +### 1. 回调地狱(Callback Hell) + +当多个异步操作需要按顺序执行时,代码会层层嵌套,形成"金字塔型"结构: + +```javascript +getUserData(userId, (user) => { + getOrders(user.id, (orders) => { + getOrderDetails(orders[0].id, (details) => { + getProductInfo(details.productId, (product) => { + console.log(product); + }); + }); + }); +}); +``` + +这种代码难以阅读、维护和调试。每一层嵌套都增加了认知负担,错误处理也变得异常复杂。 + +### 2. 错误处理困难 + +回调函数通常采用"错误优先"的约定(Error-First Callback): + +```javascript +fs.readFile('file.txt', (err, data) => { + if (err) { + console.error('读取失败:', err); + return; + } + console.log('文件内容:', data); +}); +``` + +虽然这种约定统一了错误处理方式,但在多层嵌套时,错误处理代码会散落在各个层级,导致大量重复和遗漏。 + +## Promise:优雅的异步解决方案 + +ES6(2015)引入了 **Promise**,它是对异步操作的一种抽象表示,代表一个尚未完成但预期将来会完成的操作。 + +### Promise 的基本用法 + +Promise 有三种状态: +- **Pending(待定)**:初始状态,操作尚未完成 +- **Fulfilled(已完成)**:操作成功完成 +- **Rejected(已拒绝)**:操作失败 + +```javascript +const fetchData = () => { + return new Promise((resolve, reject) => { + setTimeout(() => { + const success = true; + if (success) { + resolve("✅ 数据加载成功"); + } else { + reject("❌ 数据加载失败"); + } + }, 1000); + }); +}; + +fetchData() + .then(data => { + console.log(data); + return "处理后的数据"; + }) + .then(processedData => { + console.log(processedData); + }) + .catch(err => { + console.error("错误:", err); + }) + .finally(() => { + console.log("无论成功还是失败,都会执行"); + }); +``` + +### Promise 链式调用 + +Promise 的最大优势在于支持**链式调用**,解决了回调地狱问题: + +```javascript +getUserData(userId) + .then(user => getOrders(user.id)) + .then(orders => getOrderDetails(orders[0].id)) + .then(details => getProductInfo(details.productId)) + .then(product => console.log(product)) + .catch(err => console.error("出错了:", err)); +``` + +每个 `.then()` 接收前一个 Promise 的返回值,并返回一个新的 Promise,形成清晰的线性流程。 + +### Promise.all 和 Promise.race + +Promise 还提供了组合多个异步操作的方法: + +```javascript +// 并行执行多个 Promise,全部完成后返回 +const promises = [ + fetchUserData(), + fetchUserSettings(), + fetchUserNotifications() +]; + +Promise.all(promises) + .then(([user, settings, notifications]) => { + console.log("所有数据加载完成"); + }) + .catch(err => console.error("任一失败:", err)); + +// 返回最快完成的 Promise +Promise.race([ + fetchData(), + new Promise((_, reject) => + setTimeout(() => reject("超时"), 5000) + ) +]) + .then(data => console.log(data)) + .catch(err => console.error(err)); +``` + +## Async/Await:异步代码的同步写法 + +ES2017 引入了 **async/await**,它是 Promise 的语法糖,让异步代码看起来像同步代码一样直观: + +```javascript +async function loadUserData(userId) { + try { + const user = await getUserData(userId); + const orders = await getOrders(user.id); + const details = await getOrderDetails(orders[0].id); + const product = await getProductInfo(details.productId); + + return product; + } catch (err) { + console.error("加载用户数据失败:", err); + throw err; + } finally { + console.log("数据加载流程结束"); + } +} + +// 调用 async 函数 +loadUserData(123) + .then(product => console.log(product)) + .catch(err => console.error(err)); +``` + +### async/await 的优势 + +1. **代码更扁平**:消除了 Promise 链的嵌套 +2. **错误处理更自然**:使用 `try/catch`,符合同步代码的习惯 +3. **调试更方便**:可以在 await 处设置断点 +4. **条件语句更直观**: + +```javascript +async function fetchData(shouldFetchDetails) { + const user = await getUserData(); + + // 条件执行异步操作 + if (shouldFetchDetails) { + const details = await getDetails(user.id); + return { user, details }; + } + + return { user }; +} +``` + +### 并行执行 await + +默认情况下,await 会按顺序执行,但可以通过 `Promise.all` 实现并行: + +```javascript +async function loadDashboard() { + // 串行执行(较慢) + // const user = await getUserData(); + // const posts = await getPosts(); + // const notifications = await getNotifications(); + + // 并行执行(更快) + const [user, posts, notifications] = await Promise.all([ + getUserData(), + getPosts(), + getNotifications() + ]); + + return { user, posts, notifications }; +} +``` + +## 事件循环:JavaScript 的并发心脏 + +要真正理解 JavaScript 的异步机制,必须深入理解**事件循环**(Event Loop)。事件循环是 JavaScript 运行时(如浏览器或 Node.js)的核心机制,负责协调同步代码和异步回调的执行。 + +### 调用栈(Call Stack) + +调用栈是一个后进先出(LIFO)的数据结构,用于追踪程序的执行位置。当函数被调用时,它被压入栈顶;当函数返回时,它从栈顶弹出。 + +### 任务队列(Task Queue) + +异步操作完成后,其回调函数不会立即执行,而是被放入**任务队列**中等待。事件循环不断地检查调用栈是否为空,如果为空,则将任务队列中的第一个任务推入调用栈执行。 + +### 宏任务与微任务 + +JavaScript 的任务队列分为两类: + +| 类型 | 包含 | 优先级 | +|------|------|--------| +| **宏任务(Macrotask)** | `setTimeout`、`setInterval`、I/O 操作、UI 渲染 | 低 | +| **微任务(Microtask)** | `Promise.then()`、`Promise.catch()`、`MutationObserver`、`queueMicrotask()` | 高 | + +**执行规则**: +1. 执行当前宏任务(同步代码) +2. 执行所有微任务(清空微任务队列) +3. 渲染 UI(如果需要) +4. 从宏任务队列取出下一个任务,重复步骤 1 + +### 经典示例分析 + +```javascript +console.log("1️⃣ 同步代码"); + +setTimeout(() => { + console.log("2️⃣ setTimeout(宏任务)"); +}, 0); + +Promise.resolve().then(() => { + console.log("3️⃣ Promise.then(微任务)"); +}); + +console.log("4️⃣ 同步代码结束"); +``` + +输出顺序: +``` +1️⃣ 同步代码 +4️⃣ 同步代码结束 +3️⃣ Promise.then(微任务) +2️⃣ setTimeout(宏任务) +``` + +**执行流程解析**: +1. `console.log("1️⃣")` 立即执行 +2. `setTimeout` 的回调被放入宏任务队列 +3. `Promise.resolve().then()` 的回调被放入微任务队列 +4. `console.log("4️⃣")` 立即执行 +5. 同步代码执行完毕,检查微任务队列,执行 Promise 回调 +6. 微任务队列为空,从宏任务队列取出 setTimeout 回调执行 + +### 更复杂的示例 + +```javascript +console.log("Start"); + +setTimeout(() => { + console.log("Timeout 1"); + Promise.resolve().then(() => { + console.log("Promise inside timeout"); + }); +}, 0); + +setTimeout(() => { + console.log("Timeout 2"); +}, 0); + +Promise.resolve().then(() => { + console.log("Promise 1"); +}).then(() => { + console.log("Promise 2"); +}); + +console.log("End"); +``` + +输出: +``` +Start +End +Promise 1 +Promise 2 +Timeout 1 +Promise inside timeout +Timeout 2 +``` + +## 现代异步模式 + +### 1. for-await-of 循环 + +ES2018 引入了异步迭代器,可以优雅地遍历异步数据源: + +```javascript +async function* fetchPages() { + for (let i = 1; i <= 3; i++) { + yield await fetch(`/api/page/${i}`).then(r => r.json()); + } +} + +(async () => { + for await (const page of fetchPages()) { + console.log(page); + } +})(); +``` + +### 2. AbortController 取消请求 + +```javascript +const controller = new AbortController(); + +fetch('/api/data', { signal: controller.signal }) + .then(response => response.json()) + .then(data => console.log(data)) + .catch(err => { + if (err.name === 'AbortError') { + console.log('请求已被取消'); + } + }); + +// 5 秒后取消请求 +setTimeout(() => controller.abort(), 5000); +``` + +### 3. Top-level await(ES2022) + +```javascript +// 模块顶层直接使用 await +const data = await fetch('/api/config').then(r => r.json()); +export { data }; +``` + +## 总结 + +JavaScript 的异步编程经历了从回调到 Promise,再到 async/await 的演变,每一步都让代码更加优雅和易于维护。 + +| 机制 | 优点 | 缺点 | 适用场景 | +|------|------|------|---------| +| 回调函数 | 简单直观 | 回调地狱、错误处理困难 | 简单的异步操作 | +| Promise | 链式调用、组合方便 | 仍有一定嵌套 | 多步骤异步流程 | +| async/await | 同步写法、易调试 | 需要理解底层 Promise | 复杂异步逻辑 | + +**关键要点**: +- 微任务优先级高于宏任务 +- `await` 后面的代码会被放入微任务队列 +- 使用 `Promise.all` 并行执行多个异步操作 +- 始终使用 `try/catch` 处理 async/await 中的错误 + +> 深入理解事件循环是掌握 JavaScript 异步编程的关键。推荐阅读:[MDN - 使用 Promises](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises) + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '5 days', + NOW() - INTERVAL '5 days', + NOW() - INTERVAL '5 days' +), +( + 1, + 'Go 并发模式完全指南:Goroutine、Channel 与 Select', + 'go-concurrency-patterns', + 'Go 语言以简洁的并发模型著称,本文详细介绍 goroutine、channel、select 和各种并发设计模式,帮助你写出高性能的并发程序。', + $doc$ +# Go 并发模式完全指南:Goroutine、Channel 与 Select + +Go 语言自 2009 年发布以来,凭借其**简洁、高效、原生支持并发**的特性,迅速成为云原生时代的首选语言之一。Docker、Kubernetes、Prometheus 等知名项目均使用 Go 编写。Go 语言最引人注目的特性之一,就是其内置的轻量级线程 **goroutine** 和通信原语 **channel**,它们让并发编程变得简单而优雅。 + +与操作系统线程(通常占用数 MB 内存)不同,goroutine 的初始栈仅有 **2KB**,并且可以根据需要动态增长和收缩。这意味着在单个 Go 程序中可以轻松创建数十万个 goroutine,而不会耗尽系统资源。 + +本文将系统介绍 Go 的并发原语和常见并发模式,帮助你写出高效、健壮的并发程序。 + +## Goroutine:轻量级并发单元 + +在 Go 中,启动一个并发任务只需在函数调用前加上 `go` 关键字: + +```go +package main + +import ( + "fmt" + "time" +) + +func say(s string, times int) { + for i := 0; i < times; i++ { + time.Sleep(100 * time.Millisecond) + fmt.Printf("%s (第 %d 次)\\n", s, i+1) + } +} + +func main() { + // 启动两个 goroutine 并发执行 + go say("🌏 world", 3) + go say("👋 hello", 3) + + // 主 goroutine 等待一段时间,让子 goroutine 完成 + time.Sleep(1 * time.Second) + fmt.Println("主程序结束") +} +``` + +输出(顺序可能不同): +``` +👋 hello (第 1 次) +🌏 world (第 1 次) +🌏 world (第 2 次) +👋 hello (第 2 次) +👋 hello (第 3 次) +🌏 world (第 3 次) +主程序结束 +``` + +### 使用 WaitGroup 等待 goroutine 完成 + +上面的示例使用了 `time.Sleep` 来等待 goroutine,这种方式不够精确。Go 提供了 `sync.WaitGroup` 来优雅地等待一组 goroutine 完成: + +```go +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, wg *sync.WaitGroup) { + defer wg.Done() // 完成时减少计数器 + + fmt.Printf("🚀 Worker %d 开始工作\\n", id) + time.Sleep(time.Second) + fmt.Printf("✅ Worker %d 完成工作\\n", id) +} + +func main() { + var wg sync.WaitGroup + + for i := 1; i <= 3; i++ { + wg.Add(1) // 增加计数器 + go worker(i, &wg) + } + + wg.Wait() // 等待所有 goroutine 完成 + fmt.Println("所有工作已完成") +} +``` + +## Channel:goroutine 之间的通信桥梁 + +Go 语言的设计哲学是:**不要通过共享内存来通信,而要通过通信来共享内存**(Do not communicate by sharing memory; instead, share memory by communicating.)。Channel 正是这一哲学的核心实现。 + +Channel 是一种类型安全的队列,用于在 goroutine 之间传递数据。 + +### 创建 Channel + +```go +// 无缓冲 channel(同步通信) +ch := make(chan int) + +// 有缓冲 channel(异步通信,容量为 5) +ch := make(chan int, 5) +``` + +### 无缓冲 Channel + +无缓冲 channel 在发送和接收时会阻塞,直到对方准备好: + +```go +package main + +import "fmt" + +func main() { + ch := make(chan string) + + go func() { + fmt.Println("📤 发送消息...") + ch <- "Hello from goroutine!" + fmt.Println("📤 发送完成") + }() + + msg := <-ch // 阻塞等待接收 + fmt.Println("📨 收到:", msg) +} +``` + +### 有缓冲 Channel + +有缓冲 channel 允许在阻塞前发送指定数量的数据: + +```go +package main + +import "fmt" + +func main() { + ch := make(chan int, 3) + + // 发送数据(不会阻塞,因为缓冲区未满) + ch <- 1 + ch <- 2 + ch <- 3 + + // ch <- 4 // 如果取消注释,此行会阻塞,因为缓冲区已满 + + // 接收数据 + fmt.Println(<-ch) // 1 + fmt.Println(<-ch) // 2 + fmt.Println(<-ch) // 3 +} +``` + +### 关闭 Channel + +当发送方不再发送数据时,应该关闭 channel: + +```go +package main + +import "fmt" + +func producer(ch chan<- int) { + for i := 0; i < 5; i++ { + ch <- i + } + close(ch) // 关闭 channel +} + +func main() { + ch := make(chan int) + go producer(ch) + + // 使用 range 遍历 channel,自动检测关闭 + for value := range ch { + fmt.Printf("收到: %d\\n", value) + } + fmt.Println("Channel 已关闭") +} +``` + +### 单向 Channel + +Go 支持单向 channel 类型,用于限制 channel 的使用方向: + +```go +func producer(ch chan<- int) { // 只发送 + ch <- 42 +} + +func consumer(ch <-chan int) { // 只接收 + fmt.Println(<-ch) +} +``` + +## Select:多路复用 + +`select` 语句是 Go 并发编程的瑞士军刀,它允许你同时等待多个 channel 操作,类似于网络编程中的 `select()` 系统调用: + +```go +package main + +import ( + "fmt" + "time" +) + +func main() { + ch1 := make(chan string) + ch2 := make(chan string) + + go func() { + time.Sleep(1 * time.Second) + ch1 <- "来自 channel 1" + }() + + go func() { + time.Sleep(2 * time.Second) + ch2 <- "来自 channel 2" + }() + + // 等待两个 channel 中的任意一个 + select { + case msg1 := <-ch1: + fmt.Println(msg1) + case msg2 := <-ch2: + fmt.Println(msg2) + } +} +``` + +### 超时处理 + +```go +select { +case res := <-c1: + fmt.Println("结果:", res) +case <-time.After(1 * time.Second): + fmt.Println("⏰ 超时!") +} +``` + +### 非阻塞操作 + +```go +select { +case ch <- value: + fmt.Println("发送成功") +default: + fmt.Println("channel 已满,跳过") +} +``` + +### 随机选择 + +当多个 case 同时就绪时,select 会**随机**选择一个执行: + +```go +ch := make(chan int, 2) +ch <- 1 +ch <- 2 + +select { +case <-ch: + fmt.Println("收到数据 1") +case <-ch: + fmt.Println("收到数据 2") +} +// 随机输出其中一个 +``` + +## 常见并发模式 + +### 1. 生产者-消费者模式 + +```go +package main + +import ( + "fmt" + "time" +) + +func producer(id int, ch chan<- int) { + for i := 0; i < 3; i++ { + item := id*10 + i + fmt.Printf("🏭 生产者 %d 生产: %d\\n", id, item) + ch <- item + time.Sleep(100 * time.Millisecond) + } +} + +func consumer(id int, ch <-chan int) { + for item := range ch { + fmt.Printf("🛒 消费者 %d 消费: %d\\n", id, item) + time.Sleep(200 * time.Millisecond) + } +} + +func main() { + ch := make(chan int, 5) + + // 启动 2 个生产者 + for i := 1; i <= 2; i++ { + go producer(i, ch) + } + + // 启动 3 个消费者 + for i := 1; i <= 3; i++ { + go consumer(i, ch) + } + + time.Sleep(3 * time.Second) + close(ch) + time.Sleep(1 * time.Second) +} +``` + +### 2. Worker Pool(工作池) + +```go +package main + +import ( + "fmt" + "sync" + "time" +) + +func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) { + defer wg.Done() + for job := range jobs { + fmt.Printf("👷 Worker %d 处理任务 %d\\n", id, job) + time.Sleep(time.Second) // 模拟工作 + results <- job * 2 + } +} + +func main() { + const numJobs = 10 + const numWorkers = 3 + + jobs := make(chan int, numJobs) + results := make(chan int, numJobs) + + var wg sync.WaitGroup + for w := 1; w <= numWorkers; w++ { + wg.Add(1) + go worker(w, jobs, results, &wg) + } + + for j := 1; j <= numJobs; j++ { + jobs <- j + } + close(jobs) + + wg.Wait() + close(results) + + for result := range results { + fmt.Printf("📊 结果: %d\\n", result) + } +} +``` + +### 3. Pipeline(管道) + +```go +package main + +import "fmt" + +func gen(nums ...int) <-chan int { + out := make(chan int) + go func() { + for _, n := range nums { + out <- n + } + close(out) + }() + return out +} + +func sq(in <-chan int) <-chan int { + out := make(chan int) + go func() { + for n := range in { + out <- n * n + } + close(out) + }() + return out +} + +func main() { + // 设置 pipeline: gen -> sq + for result := range sq(sq(gen(2, 3, 4))) { + fmt.Println(result) // 16, 81, 256 (先平方再平方) + } +} +``` + +### 4. Fan-out/Fan-in(扇出/扇入) + +```go +func merge(channels ...<-chan int) <-chan int { + var wg sync.WaitGroup + out := make(chan int) + + output := func(c <-chan int) { + defer wg.Done() + for n := range c { + out <- n + } + } + + wg.Add(len(channels)) + for _, c := range channels { + go output(c) + } + + go func() { + wg.Wait() + close(out) + }() + + return out +} +``` + +## Context:请求级控制 + +Go 1.7 引入了 `context` 包,用于在 goroutine 之间传递截止时间、取消信号和请求范围的值: + +```go +package main + +import ( + "context" + "fmt" + "time" +) + +func slowOperation(ctx context.Context) (string, error) { + select { + case <-time.After(2 * time.Second): + return "✅ 操作完成", nil + case <-ctx.Done(): + return "", ctx.Err() + } +} + +func main() { + // 设置 1 秒超时 + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + result, err := slowOperation(ctx) + if err != nil { + fmt.Println("❌ 错误:", err) // context deadline exceeded + return + } + fmt.Println(result) +} +``` + +### Context 链式传递 + +```go +func main() { + ctx := context.Background() + + // 添加超时 + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + // 添加取消信号 + ctx, cancel = context.WithCancel(ctx) + defer cancel() + + // 添加请求 ID + ctx = context.WithValue(ctx, "requestID", "abc-123") + + processRequest(ctx) +} +``` + +## 并发模式对比 + +| 模式 | 描述 | 使用场景 | 关键原语 | +|------|------|---------|---------| +| 生产者-消费者 | 通过 channel 传递数据 | 任务队列、消息处理 | `chan`, `range` | +| Worker Pool | 固定数量的 worker 处理任务 | CPU 密集型任务 | `sync.WaitGroup` | +| Pipeline | 多个 stage 串联处理 | 数据流处理 | `chan` 返回 | +| Fan-out | 多路分发 | 并行处理 | 多个 goroutine | +| Fan-in | 多路合并 | 聚合结果 | `select`, `sync.WaitGroup` | +| 超时控制 | 限制操作时间 | 网络请求、外部调用 | `context`, `time.After` | + +## 总结 + +Go 的并发模型以其简洁和高效著称。核心要点: + +1. **Goroutine** 是轻量级线程,通过 `go` 关键字启动 +2. **Channel** 是 goroutine 之间的通信方式,而不是共享内存 +3. **Select** 实现多路复用和超时控制 +4. **Context** 管理请求级的生命周期 +5. **Sync 包** 提供了锁、WaitGroup、Once、Pool 等同步原语 + +> **Go 并发黄金法则**:先通过 channel 思考,只有在必要时才使用共享内存和锁。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '4 days', + NOW() - INTERVAL '4 days', + NOW() - INTERVAL '4 days' +), +( + 1, + 'TypeScript 高级类型体操:从条件类型到模板字面量', + 'typescript-type-gymnastics', + '探索 TypeScript 类型系统的极限,从条件类型到映射类型,再到模板字面量类型和类型推断,让你的代码在编译期就获得强大的类型保障。', + $doc$ +# TypeScript 高级类型体操:从条件类型到模板字面量 + +TypeScript 的类型系统是一门**图灵完备**的语言。这意味着,在理论上,你可以使用 TypeScript 的类型系统来实现任何可计算的程序。虽然我们不建议在真实项目中过度使用复杂的类型体操,但掌握这些高级类型技巧,可以帮助你构建更加健壮、可维护的类型定义,特别是在开发库和框架时。 + +本文将从基础到高级,系统介绍 TypeScript 的类型系统特性,包括条件类型、映射类型、模板字面量类型、递归类型和类型推断等。 + +## 为什么需要高级类型? + +在日常开发中,基础类型(`string`、`number`、`boolean`)和简单的接口往往已经足够。但在以下场景中,高级类型变得至关重要: + +- **开发类型安全的库**:如 Vue、React、Redux 等框架的类型定义 +- **编写通用工具函数**:如 lodash 的类型定义 +- **实现类型转换**:如将对象的所有属性变为可选或只读 +- **约束 API 接口**:确保编译器能够推断出正确的响应类型 + +## 条件类型(Conditional Types) + +条件类型是 TypeScript 2.8 引入的特性,它允许你根据类型关系选择不同的类型: + +```typescript +type IsString = T extends string ? true : false; + +// 使用示例 +type A = IsString; // true +type B = IsString; // false +type C = IsString<"hello">; // true(字面量类型也是 string 的子类型) +``` + +### extends 关键字 + +在条件类型中,`extends` 表示"是否是...的子类型": + +```typescript +type IsArray = T extends any[] ? true : false; + +type D = IsArray; // true +type E = IsArray; // false +``` + +### infer 关键字:类型推断 + +`infer` 是 TypeScript 中最强大的关键字之一,它允许你在条件类型中"提取"类型: + +```typescript +// 提取数组元素类型 +type ElementType = T extends (infer U)[] ? U : never; + +type F = ElementType; // string +type G = ElementType; // number + +// 提取函数返回值类型 +type ReturnType = T extends (...args: any[]) => infer R ? R : never; + +function getUser() { + return { id: 1, name: "Alice" }; +} + +type User = ReturnType; // { id: number; name: string; } + +// 提取 Promise 的解析类型 +type UnwrapPromise = T extends Promise ? U : T; + +type H = UnwrapPromise>; // string +``` + +### 内置条件类型 + +TypeScript 标准库已经提供了许多基于条件类型的工具类型: + +| 类型 | 作用 | 示例 | +|------|------|------| +| `Exclude` | 从 T 中排除 U | `Exclude<'a' \| 'b', 'a'>` → `'b'` | +| `Extract` | 从 T 中提取 U | `Extract<'a' \| 'b', 'a' \| 'c'>` → `'a'` | +| `NonNullable` | 排除 null 和 undefined | `NonNullable` → `string` | +| `ReturnType` | 获取函数返回类型 | `ReturnType<() => number>` → `number` | +| `Parameters` | 获取函数参数类型 | `Parameters<(a: string) => void>` → `[string]` | +| `InstanceType` | 获取构造函数实例类型 | `InstanceType` → `Date` | + +## 映射类型(Mapped Types) + +映射类型允许你基于已有类型创建新类型,通过遍历属性键来转换每个属性: + +### 基础映射类型 + +```typescript +type Readonly = { + readonly [P in keyof T]: T[P]; +}; + +type Partial = { + [P in keyof T]?: T[P]; +}; + +type Required = { + [P in keyof T]-?: T[P]; // -? 移除可选性 +}; + +// 使用示例 +interface User { + name: string; + age: number; +} + +type ReadonlyUser = Readonly; +// { readonly name: string; readonly age: number; } + +type PartialUser = Partial; +// { name?: string; age?: number; } +``` + +### 键重映射(Key Remapping) + +TypeScript 4.1 引入了 `as` 关键字,允许在映射类型中重命名键: + +```typescript +// 将每个属性名添加 "get" 前缀,类型变为函数 +type Getters = { + [K in keyof T as `get${Capitalize}`]: () => T[K]; +}; + +interface Person { + name: string; + age: number; +} + +type PersonGetters = Getters; +// { getName: () => string; getAge: () => number; } +``` + +### 过滤属性 + +```typescript +// 只保留 string 类型的属性 +type StringProperties = { + [K in keyof T as T[K] extends string ? K : never]: T[K]; +}; + +interface User { + name: string; + age: number; + email: string; +} + +type StringUserProps = StringProperties; +// { name: string; email: string; } +``` + +## 模板字面量类型(Template Literal Types) + +TypeScript 4.1 引入了模板字面量类型,它允许你通过字符串字面量类型来构造新类型: + +### 基础用法 + +```typescript +type EventName = `on${Capitalize}`; + +type ClickEvent = EventName<"click">; // "onClick" +type HoverEvent = EventName<"mouseOver">; // "onMouseOver" +``` + +### 联合类型的组合 + +当模板字面量类型与联合类型结合使用时,会产生**笛卡尔积**效果: + +```typescript +type Horizontal = "left" | "center" | "right"; +type Vertical = "top" | "center" | "bottom"; + +type Alignment = `${Horizontal}-${Vertical}`; +// "left-top" | "left-center" | "left-bottom" | +// "center-top" | "center-center" | "center-bottom" | +// "right-top" | "right-center" | "right-bottom" +``` + +### 实际应用:CSS 属性类型 + +```typescript +type CSSProperty = "margin" | "padding"; +type CSSDirection = "top" | "right" | "bottom" | "left"; + +type CSSKey = `${CSSProperty}${Capitalize}` | CSSProperty; +// "margin" | "padding" | "marginTop" | "marginRight" | ... +``` + +## 递归类型 + +TypeScript 支持递归类型定义,这在处理嵌套数据结构时非常有用: + +```typescript +// 深度只读类型 +type DeepReadonly = { + readonly [P in keyof T]: T[P] extends object + ? DeepReadonly + : T[P]; +}; + +interface NestedUser { + name: string; + address: { + city: string; + coordinates: { + lat: number; + lng: number; + }; + }; +} + +type DeepReadonlyUser = DeepReadonly; +// 所有嵌套属性都变为 readonly + +// 深度 Partial 类型 +type DeepPartial = { + [P in keyof T]?: T[P] extends object + ? DeepPartial + : T[P]; +}; + +// 扁平化对象类型(将嵌套属性用点号连接) +type Flatten = { + [K in keyof T as K extends string + ? Prefix extends "" + ? K + : `${Prefix & string}.${K}` + : never + ]: T[K] extends object ? Flatten : T[K]; +}; +``` + +## 类型守卫与类型收窄 + +TypeScript 的类型系统不仅在编译期工作,还能通过类型守卫在运行时收窄类型: + +```typescript +// typeof 类型守卫 +function processValue(value: string | number) { + if (typeof value === "string") { + // 此分支中 value 的类型为 string + return value.toUpperCase(); + } else { + // 此分支中 value 的类型为 number + return value.toFixed(2); + } +} + +// 自定义类型守卫 +type Cat = { kind: "cat"; meow: () => void }; +type Dog = { kind: "dog"; bark: () => void }; +type Animal = Cat | Dog; + +function isCat(animal: Animal): animal is Cat { + return animal.kind === "cat"; +} + +function makeSound(animal: Animal) { + if (isCat(animal)) { + animal.meow(); + } else { + animal.bark(); + } +} +``` + +## 实用类型工具库 + +基于上述类型特性,你可以构建强大的类型工具库: + +```typescript +// 将对象的所有嵌套属性路径提取为联合类型 +type Path = K extends string + ? T[K] extends object + ? `${K}` | `${K}.${Path}` + : `${K}` + : never; + +// 安全的深度属性访问类型 +type DeepPick = P extends `${infer K}.${infer Rest}` + ? K extends keyof T + ? { [key in K]: DeepPick } + : never + : P extends keyof T + ? { [key in P]: T[P] } + : never; +``` + +## 总结 + +TypeScript 的类型系统提供了丰富的工具来构建类型安全的应用程序: + +| 特性 | 版本 | 作用 | +|------|------|------| +| 条件类型 | 2.8 | 基于类型关系选择类型 | +| infer | 2.8 | 在条件类型中提取类型 | +| 映射类型 | 2.1 | 遍历属性键转换类型 | +| 模板字面量 | 4.1 | 构造字符串字面量类型 | +| 键重映射 | 4.1 | 在映射中重命名属性键 | +| 递归类型 | 3.7+ | 处理嵌套数据结构 | + +> ⚠️ **温馨提示**:类型体操虽有趣,但过度使用会降低代码可读性和编译性能。遵循"简单优于复杂"的原则,在合适的场景使用高级类型。对于大多数业务代码,基础类型和简单的接口已经足够。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '3 days', + NOW() - INTERVAL '3 days', + NOW() - INTERVAL '3 days' +), +( + 1, + 'Java 泛型深度解析:从基础到通配符的PECS原则', + 'java-generics-cheatsheet', + 'Java 泛型是类型安全的基石,本文深入讲解泛型类、泛型方法、通配符及其上界下界,以及PECS原则在实际编程中的应用。', + $doc$ +# Java 泛型深度解析:从基础到通配符的 PECS 原则 + +Java 泛型(Generics)是自 JDK 5 引入的一项重要特性,它为 Java 语言带来了**编译期类型检查**和**类型安全**的能力。在没有泛型之前,Java 集合只能存储 `Object` 类型,取出数据时需要进行强制类型转换,这不仅繁琐,而且容易在运行时抛出 `ClassCastException`。 + +本文将从基础概念出发,深入探讨 Java 泛型的核心机制,包括泛型类、泛型方法、通配符、类型擦除,以及实际开发中最常用的 PECS 原则。 + +## 泛型的基础概念 + +### 泛型类 + +泛型类允许你在定义类时使用类型参数,这些类型参数在创建实例时被具体化: + +```java +// 定义泛型类 +public class Box { + private T value; + + public void set(T value) { + this.value = value; + } + + public T get() { + return value; + } + + public static void main(String[] args) { + // 创建 String 类型的 Box + Box stringBox = new Box<>(); + stringBox.set("Hello Generics"); + String str = stringBox.get(); // 无需类型转换 + + // 创建 Integer 类型的 Box + Box intBox = new Box<>(); + intBox.set(42); + Integer num = intBox.get(); + } +} +``` + +### 泛型方法 + +泛型方法允许你在普通类或泛型类中定义带有类型参数的方法: + +```java +public class GenericMethodExample { + + // 泛型方法 + public void printArray(T[] array) { + for (T element : array) { + System.out.println(element); + } + } + + // 泛型方法 with 返回值 + public T getFirst(T[] array) { + return array.length > 0 ? array[0] : null; + } + + // 泛型方法 with 多个类型参数 + public void printPair(K key, V value) { + System.out.println("Key: " + key + ", Value: " + value); + } + + public static void main(String[] args) { + GenericMethodExample example = new GenericMethodExample(); + + String[] names = {"Alice", "Bob", "Charlie"}; + example.printArray(names); + + Integer first = example.getFirst(new Integer[]{1, 2, 3}); + example.printPair("ID", 1001); + } +} +``` + +### 类型参数的约束 + +你可以使用 `extends` 关键字对类型参数进行约束: + +```java +// T 必须是 Number 的子类 +public class NumberBox { + private T value; + + public double getDoubleValue() { + return value.doubleValue(); + } +} + +// 使用 +NumberBox intBox = new NumberBox<>(); // ✅ +NumberBox doubleBox = new NumberBox<>(); // ✅ +// NumberBox stringBox = new NumberBox<>(); // ❌ 编译错误 +``` + +## 通配符(Wildcards) + +泛型中的通配符 `?` 表示**未知类型**,它提供了更灵活的类型兼容性。 + +### 无界通配符 `?` + +```java +public void printList(List list) { + for (Object obj : list) { + System.out.println(obj); + } +} + +// 可以接受任何类型的 List +printList(new ArrayList()); +printList(new ArrayList()); +``` + +### 上界通配符 `? extends T` + +`? extends T` 表示**T 的子类型**,适用于读取数据的场景: + +```java +// 可以接受 Number 及其子类型的 List +public double sumNumbers(List numbers) { + double sum = 0; + for (Number num : numbers) { + sum += num.doubleValue(); + } + return sum; +} + +// 使用 +List ints = Arrays.asList(1, 2, 3); +List doubles = Arrays.asList(1.1, 2.2, 3.3); + +sumNumbers(ints); // ✅ Integer 是 Number 的子类 +sumNumbers(doubles); // ✅ Double 是 Number 的子类 +``` + +### 下界通配符 `? super T` + +`? super T` 表示**T 的父类型**,适用于写入数据的场景: + +```java +// 可以接受 Integer 及其父类型的 List +public void addIntegers(List list) { + list.add(1); + list.add(2); + list.add(3); +} + +// 使用 +List numbers = new ArrayList<>(); +List objects = new ArrayList<>(); + +addIntegers(numbers); // ✅ Number 是 Integer 的父类 +addIntegers(objects); // ✅ Object 是 Integer 的父类 +// addIntegers(new ArrayList()); // ❌ 编译错误 +``` + +## PECS 原则 + +PECS 是 Java 泛型中最重要也最实用的原则,它是 **Producer-Extends, Consumer-Super** 的缩写: + +| 原则 | 含义 | 使用场景 | +|------|------|---------| +| **Producer-Extends** | 如果数据从集合中产出(读取),使用 `? extends T` | 方法参数用于读取 | +| **Consumer-Super** | 如果数据被消费(写入),使用 `? super T` | 方法参数用于写入 | + +### 实际案例:Collections.copy + +Java 标准库中的 `Collections.copy` 方法完美诠释了 PECS 原则: + +```java +public static void copy(List dest, List src) { + for (int i = 0; i < src.size(); i++) { + dest.set(i, src.get(i)); + } +} +``` + +分析: +- `src`(源列表)是**生产者**,从中读取数据,所以用 `? extends T` +- `dest`(目标列表)是**消费者**,向其中写入数据,所以用 `? super T` + +### 实战示例 + +```java +public class PECSExample { + + // Producer: 从列表中读取数据 + public static double sumOfList(List list) { + double sum = 0.0; + for (Number n : list) { + sum += n.doubleValue(); + } + return sum; + } + + // Consumer: 向列表中写入数据 + public static void addNumbers(List list) { + for (int i = 1; i <= 5; i++) { + list.add(i); + } + } + + public static void main(String[] args) { + // Producer 示例 + List ints = Arrays.asList(1, 2, 3); + List doubles = Arrays.asList(1.1, 2.2, 3.3); + + System.out.println(sumOfList(ints)); // 6.0 + System.out.println(sumOfList(doubles)); // 6.6 + + // Consumer 示例 + List numbers = new ArrayList<>(); + addNumbers(numbers); + System.out.println(numbers); // [1, 2, 3, 4, 5] + + List objects = new ArrayList<>(); + addNumbers(objects); + System.out.println(objects); // [1, 2, 3, 4, 5] + } +} +``` + +## 类型擦除(Type Erasure) + +Java 泛型采用了**类型擦除**机制来实现向后兼容。在编译期,所有泛型信息都会被擦除,替换为它们的上界(通常是 `Object`): + +```java +// 编译前 +List strings = new ArrayList<>(); +String s = strings.get(0); + +// 编译后(类型擦除) +List strings = new ArrayList(); +String s = (String) strings.get(0); // 自动插入类型转换 +``` + +### 类型擦除的影响 + +1. **不能使用基本类型**:`List` 是错误的,必须使用 `List` +2. **运行时类型检查受限**:`instanceof List` 是非法的 +3. **不能创建泛型数组**:`new T[10]` 是非法的 +4. **可以通过反射绕过泛型检查**: + +```java +List strings = new ArrayList<>(); +// strings.add(42); // 编译错误 + +// 但可以通过反射绕过 +Method m = strings.getClass().getMethod("add", Object.class); +m.invoke(strings, 42); // 运行时成功添加 Integer! +``` + +## 泛型与继承 + +泛型类型之间**不协变**(not covariant): + +```java +List objects = new ArrayList(); // ❌ 编译错误 +``` + +虽然 `String` 是 `Object` 的子类,但 `List` 并不是 `List` 的子类。这是为了防止以下运行时错误: + +```java +// 假设允许协变 +List strings = new ArrayList<>(); +List objects = strings; // 假设可以 +objects.add(42); // 向字符串列表中添加整数! +String s = strings.get(0); // ClassCastException! +``` + +## 总结 + +Java 泛型是类型安全的基石,掌握它需要理解以下核心概念: + +| 概念 | 说明 | 示例 | +|------|------|------| +| 泛型类 | 类级别的类型参数 | `class Box` | +| 泛型方法 | 方法级别的类型参数 | ` T method(T arg)` | +| `? extends T` | 上界通配符,用于读取 | `List` | +| `? super T` | 下界通配符,用于写入 | `List` | +| PECS | Producer-Extends, Consumer-Super | `copy(dest, src)` | +| 类型擦除 | 编译期擦除泛型信息 | `List` → `List` | + +> **最佳实践**:始终遵循 PECS 原则设计 API,使用通配符增加 API 的灵活性,但不要过度使用复杂的泛型嵌套,保持代码的可读性。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '2 days', + NOW() - INTERVAL '2 days', + NOW() - INTERVAL '2 days' +), +( + 1, + 'C++ RAII 与智能指针:现代 C++ 内存管理完全指南', + 'cpp-raii', + '深入理解 C++ 的 RAII 原则、智能指针(unique_ptr、shared_ptr、weak_ptr)以及三/五/零法则,写出内存安全的现代 C++ 代码。', + $doc$ +# C++ RAII 与智能指针:现代 C++ 内存管理完全指南 + +C++ 是一门赋予程序员极大自由度的语言,这种自由既带来了高性能,也带来了内存管理的挑战。手动管理内存容易引发内存泄漏、悬垂指针、双重释放等问题。为了解决这些问题,C++ 社区发展出了 **RAII**(Resource Acquisition Is Initialization,资源获取即初始化)原则,以及一套智能指针工具。掌握这些现代 C++ 特性,是编写健壮 C++ 代码的关键。 + +本文将系统介绍 RAII 原则、智能指针的使用、三/五/零法则,以及实际开发中的最佳实践。 + +## 什么是 RAII? + +RAII 是 C++ 的核心编程范式,由 Bjarne Stroustrup 提出。其核心思想是:**将资源的生命周期与对象的生命周期绑定,在对象构造时获取资源,在对象析构时释放资源**。 + +由于 C++ 的栈对象在离开作用域时会自动调用析构函数,这一机制天然地保证了资源的正确释放,即使在发生异常的情况下也是如此。 + +### 基础示例:文件句柄 + +```cpp +#include +#include + +class FileHandle { + FILE* file; + +public: + // 构造函数:获取资源 + explicit FileHandle(const char* filename, const char* mode = "r") + : file(std::fopen(filename, mode)) { + if (!file) { + throw std::runtime_error("无法打开文件"); + } + } + + // 析构函数:释放资源 + ~FileHandle() { + if (file) { + std::fclose(file); + } + } + + // 禁用拷贝(资源不可复制) + FileHandle(const FileHandle&) = delete; + FileHandle& operator=(const FileHandle&) = delete; + + // 允许移动(C++11) + FileHandle(FileHandle&& other) noexcept : file(other.file) { + other.file = nullptr; + } + + FileHandle& operator=(FileHandle&& other) noexcept { + if (this != &other) { + if (file) std::fclose(file); + file = other.file; + other.file = nullptr; + } + return *this; + } + + FILE* get() const { return file; } +}; + +// 使用 +void processFile(const char* filename) { + FileHandle fh(filename); // 打开文件 + // 处理文件... + // 即使发生异常,fh 的析构函数也会确保文件被关闭 +} +``` + +## 三/五/零法则 + +### 三法则(Rule of Three) + +在 C++98/03 时代,如果一个类需要自定义以下三个函数中的任意一个,通常需要定义全部三个: + +1. **析构函数**(Destructor) +2. **拷贝构造函数**(Copy Constructor) +3. **拷贝赋值运算符**(Copy Assignment Operator) + +这是因为这三个函数都与资源管理密切相关。如果你需要自定义其中一个,说明你的类管理着某种资源,因此三个都需要自定义。 + +### 五法则(Rule of Five) + +C++11 引入了移动语义,五法则在三法则基础上增加了: + +4. **移动构造函数**(Move Constructor) +5. **移动赋值运算符**(Move Assignment Operator) + +```cpp +class Resource { + int* data; + size_t size; + +public: + // 构造函数 + explicit Resource(size_t n) : data(new int[n]), size(n) {} + + // 1. 析构函数 + ~Resource() { + delete[] data; + } + + // 2. 拷贝构造函数 + Resource(const Resource& other) : data(new int[other.size]), size(other.size) { + std::copy(other.data, other.data + size, data); + } + + // 3. 拷贝赋值运算符 + Resource& operator=(const Resource& other) { + if (this != &other) { + Resource temp(other); // 拷贝并交换惯用法 + std::swap(data, temp.data); + std::swap(size, temp.size); + } + return *this; + } + + // 4. 移动构造函数 + Resource(Resource&& other) noexcept : data(other.data), size(other.size) { + other.data = nullptr; + other.size = 0; + } + + // 5. 移动赋值运算符 + Resource& operator=(Resource&& other) noexcept { + if (this != &other) { + delete[] data; + data = other.data; + size = other.size; + other.data = nullptr; + other.size = 0; + } + return *this; + } +}; +``` + +### 零法则(Rule of Zero) + +现代 C++ 的最佳实践是**零法则**: + +> **如果一个类不需要自定义析构函数、拷贝/移动构造函数或赋值运算符,那就不要自定义。** + +通过使用智能指针和标准库容器,你可以让编译器自动生成这些函数: + +```cpp +// ✅ 遵循零法则 +class ModernResource { + std::unique_ptr data; + size_t size; + +public: + explicit ModernResource(size_t n) : data(std::make_unique(n)), size(n) {} + + // 编译器自动生成的析构函数、拷贝/移动函数都能正确工作 + // 因为 unique_ptr 已经正确管理了内存 +}; +``` + +## 智能指针 + +C++11 引入了三种智能指针,分别适用于不同的所有权模型: + +### std::unique_ptr:独占所有权 + +`unique_ptr` 表示对对象的**独占所有权**,同一时间只能有一个 `unique_ptr` 指向给定对象。当 `unique_ptr` 被销毁时,它所指向的对象也会被自动删除。 + +```cpp +#include +#include + +class Widget { +public: + Widget() { std::cout << "Widget 构造\\n"; } + ~Widget() { std::cout << "Widget 析构\\n"; } + void doSomething() { std::cout << "Widget 工作中\\n"; } +}; + +void uniquePtrDemo() { + // 创建 unique_ptr + std::unique_ptr ptr1 = std::make_unique(); + ptr1->doSomething(); + + // 转移所有权 + std::unique_ptr ptr2 = std::move(ptr1); + // ptr1 现在为空 + // ptr2->doSomething(); // ✅ + + // 自动释放 +} // Widget 在此处被销毁 + +// 工厂函数返回 unique_ptr +std::unique_ptr createWidget() { + return std::make_unique(); +} +``` + +**最佳实践**: +- 默认使用 `std::make_unique` 创建(C++14) +- 用于表示独占所有权 +- 作为函数参数传递时,使用 `std::move` 转移所有权 + +### std::shared_ptr:共享所有权 + +`shared_ptr` 通过**引用计数**实现共享所有权。多个 `shared_ptr` 可以同时指向同一个对象,当最后一个 `shared_ptr` 被销毁时,对象才被删除。 + +```cpp +#include +#include + +void sharedPtrDemo() { + // 创建 shared_ptr + std::shared_ptr ptr1 = std::make_shared(); + { + std::shared_ptr ptr2 = ptr1; // 引用计数 +1 + std::cout << "引用计数: " << ptr1.use_count() << "\\n"; // 2 + } // ptr2 销毁,引用计数 -1 + + std::cout << "引用计数: " << ptr1.use_count() << "\\n"; // 1 +} // Widget 在此处被销毁 +``` + +### std::weak_ptr:弱引用 + +`weak_ptr` 是一种**不控制对象生命周期**的智能指针。它指向一个由 `shared_ptr` 管理的对象,但不会增加引用计数。它主要用于解决 **循环引用** 问题: + +```cpp +#include +#include + +class B; // 前向声明 + +class A { +public: + std::shared_ptr b_ptr; + ~A() { std::cout << "A 析构\\n"; } +}; + +class B { +public: + // 使用 weak_ptr 避免循环引用 + std::weak_ptr a_ptr; + ~B() { std::cout << "B 析构\\n"; } +}; + +void weakPtrDemo() { + { + std::shared_ptr a = std::make_shared(); + std::shared_ptr b = std::make_shared(); + + a->b_ptr = b; + b->a_ptr = a; // weak_ptr,不增加引用计数 + + // 检查对象是否还存在 + if (auto shared = b->a_ptr.lock()) { + std::cout << "A 仍然存在\\n"; + } + } // A 和 B 都被正确析构 +} +``` + +## 智能指针对比 + +| 特性 | `unique_ptr` | `shared_ptr` | `weak_ptr` | +|------|-------------|-------------|-----------| +| 所有权 | 独占 | 共享 | 无(弱引用) | +| 引用计数 | 无 | 有 | 无 | +| 可拷贝 | ❌ | ✅ | ✅ | +| 可移动 | ✅ | ✅ | ✅ | +| 内存开销 | 最小 | 引用计数 + 控制块 | 最小 | +| 适用场景 | 独占资源 | 共享资源 | 打破循环引用 | + +## RAII 在标准库中的应用 + +C++ 标准库大量使用了 RAII 原则: + +```cpp +// 容器 +std::vector vec = {1, 2, 3}; // 自动管理内存 + +// 锁 +std::mutex mtx; +{ + std::lock_guard lock(mtx); // 自动加锁 + // 临界区... +} // 自动解锁 + +// 文件流 +{ + std::ofstream file("data.txt"); + file << "Hello, RAII!"; +} // 自动关闭文件 + +// 线程 +{ + std::thread t([]() { + std::cout << "后台任务\\n"; + }); + t.join(); // 或者使用 RAII 包装器 +} +``` + +## 总结 + +现代 C++ 的内存管理已经不再是噩梦。通过遵循以下原则,你可以写出既高效又安全的代码: + +1. **优先使用智能指针**:`unique_ptr` > `shared_ptr` > 原始指针 +2. **遵循零法则**:使用标准库工具管理资源 +3. **使用 `std::make_unique` 和 `std::make_shared`**:异常安全且高效 +4. **理解所有权语义**:明确谁拥有资源,谁只是借用 +5. **注意循环引用**:使用 `weak_ptr` 打破循环 + +> **C++ 之父的名言**:"C++ 中,资源管理即对象管理。" —— Bjarne Stroustrup + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '1 day', + NOW() - INTERVAL '1 day', + NOW() - INTERVAL '1 day' +), +( + 1, + 'Haskell 纯函数、惰性求值与 Monad:函数式编程的精髓', + 'haskell-pure-functions', + '深入理解 Haskell 的纯函数、惰性求值、高阶函数和 Monad,探索函数式编程的核心思想。', + $doc$ +# Haskell 纯函数、惰性求值与 Monad:函数式编程的精髓 + +Haskell 是一门**纯函数式编程语言**,以严格的数学基础和优雅的设计著称。与命令式语言不同,Haskell 强调函数的数学本质:**函数只是从输入到输出的映射**,没有副作用、没有状态变化、没有隐式的执行顺序。这种纯粹性带来了前所未有的代码可预测性和可组合性。 + +本文将深入探讨 Haskell 的三大核心特性:纯函数、惰性求值和 Monad,帮助你理解函数式编程的精髓。 + +## 纯函数(Pure Functions) + +纯函数是函数式编程的基石。一个函数是纯函数,当且仅当满足两个条件: + +1. **引用透明**(Referential Transparency):对于相同的输入,永远返回相同的输出 +2. **无副作用**(No Side Effects):不修改外部状态,不执行 I/O 操作 + +### 纯函数示例 + +```haskell +-- 纯函数:给定输入,总有确定的输出 +factorial :: Integer -> Integer +factorial 0 = 1 +factorial n = n * factorial (n - 1) + +-- 另一个纯函数 +square :: Num a => a -> a +square x = x * x + +-- 纯函数可以安全地被替换为其结果(引用透明) +-- square 5 总是可以被替换为 25 +``` + +### 纯函数的优势 + +| 特性 | 说明 | +|------|------| +| 可测试性 | 无需模拟外部状态,输入确定即可测试 | +| 可缓存性 | 结果可以被记忆化(memoization) | +| 可并行性 | 无共享状态,天然适合并行计算 | +| 可组合性 | 函数可以像乐高积木一样组合 | + +## 高阶函数(Higher-Order Functions) + +Haskell 中的函数是一等公民,可以作为参数传递,也可以作为返回值: + +```haskell +-- map:对列表每个元素应用函数 +map :: (a -> b) -> [a] -> [b] +doubleAll = map (* 2) +-- doubleAll [1, 2, 3] => [2, 4, 6] + +-- filter:根据条件过滤列表 +filter :: (a -> Bool) -> [a] -> [a] +evens = filter even +-- evens [1..10] => [2, 4, 6, 8, 10] + +-- foldl / foldr:列表归约 +sumList = foldl (+) 0 +-- sumList [1, 2, 3, 4] => 10 + +-- 函数组合 +(.) :: (b -> c) -> (a -> b) -> a -> c +f . g = \\x -> f (g x) + +-- 使用函数组合 +process = sum . map square . filter even +-- process [1..10] = sum (map square (filter even [1..10])) +``` + +### 自定义高阶函数 + +```haskell +-- 将函数应用两次 +applyTwice :: (a -> a) -> a -> a +applyTwice f x = f (f x) + +-- 使用 +applyTwice (+ 3) 10 -- 16 +applyTwice reverse [1, 2, 3] -- [1, 2, 3] + +-- 柯里化(Currying) +add :: Int -> Int -> Int +add x y = x + y + +-- add 5 是一个接收 Int 返回 Int 的函数 +addFive = add 5 +addFive 3 -- 8 +``` + +## 惰性求值(Lazy Evaluation) + +Haskell 默认采用**惰性求值**(Lazy Evaluation),也称为**按需调用**(Call by Need)。这意味着表达式在真正需要其值时才会被计算。 + +### 无限列表 + +惰性求值最惊人的特性之一,就是可以定义**无限数据结构**: + +```haskell +-- 无限自然数列表 +nats :: [Integer] +nats = [0..] + +-- 无限偶数列表 +evens :: [Integer] +evens = [0, 2..] + +-- 无限斐波那契数列 +fibs :: [Integer] +fibs = 0 : 1 : zipWith (+) fibs (tail fibs) + +-- 使用 take 只取有限部分 +main = do + print $ take 10 nats -- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + print $ take 10 evens -- [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] + print $ take 15 fibs -- [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377] +``` + +### 列表推导式 + +Haskell 的列表推导式(List Comprehension)是处理集合的强大工具: + +```haskell +-- 基本列表推导式 +squares = [x^2 | x <- [1..10]] +-- [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] + +-- 带过滤条件 +evenSquares = [x^2 | x <- [1..20], even x] +-- [4, 16, 36, 64, 100, 144, 196, 256, 324, 400] + +-- 多生成器 +pairs = [(x, y) | x <- [1..3], y <- ['a', 'b']] +-- [(1,'a'), (1,'b'), (2,'a'), (2,'b'), (3,'a'), (3,'b')] + +-- 使用 let 绑定 +result = [let y = x * 2 in (x, y) | x <- [1..5]] +-- [(1,2), (2,4), (3,6), (4,8), (5,10)] +``` + +### 惰性求值的实际应用 + +```haskell +-- 只计算需要的部分 +firstEvenSquareOver100 = head [x^2 | x <- [1..], even x, x^2 > 100] +-- 结果为 144(12^2),不需要计算 [1..] 的所有元素 + +-- 短路求值 +and' :: [Bool] -> Bool +and' = foldr (&&) True +-- and' (False : undefined) => False +-- 不需要计算 undefined,因为第一个 False 已经决定了结果 +``` + +## 类型系统与类型类 + +Haskell 拥有强大的静态类型系统和类型推断能力: + +```haskell +-- 代数数据类型(Algebraic Data Types) +data Shape = Circle Float + | Rectangle Float Float + | Triangle Float Float Float + deriving (Show, Eq) + +area :: Shape -> Float +area (Circle r) = pi * r * r +area (Rectangle w h) = w * h +area (Triangle a b c) = + let s = (a + b + c) / 2 + in sqrt (s * (s - a) * (s - b) * (s - c)) + +-- 类型类(Type Classes) +class Describable a where + describe :: a -> String + +instance Describable Shape where + describe (Circle r) = "半径为 " ++ show r ++ " 的圆" + describe (Rectangle w h) = show w ++ " x " ++ show h ++ " 的矩形" +``` + +## Monad:处理副作用的优雅方式 + +纯函数不能执行 I/O 操作,但程序总要与外界交互。Haskell 使用 **Monad** 来在纯函数式框架内处理副作用。 + +### Maybe Monad:处理可能失败的计算 + +```haskell +-- 安全除法 +safeDiv :: Int -> Int -> Maybe Int +safeDiv _ 0 = Nothing +safeDiv a b = Just (a `div` b) + +-- 使用 do 语法串联 Maybe 计算 +calculate :: Int -> Int -> Int -> Maybe Int +calculate x y z = do + a <- safeDiv x y -- 如果失败,整个计算返回 Nothing + b <- safeDiv a z + return (b + 1) + +-- 示例 +calculate 10 2 3 -- Just 2 +calculate 10 0 3 -- Nothing +calculate 10 2 0 -- Nothing +``` + +### IO Monad:处理输入输出 + +```haskell +-- IO 是一个 Monad,将副作用操作封装起来 +greet :: IO () +greet = do + putStrLn "请输入你的名字:" + name <- getLine + putStrLn $ "你好, " ++ name ++ "!" + +-- 使用 >>=(bind)操作符 +main :: IO () +main = putStrLn "Hello" >>= \_ -> putStrLn "World" +``` + +### 常用 Monad + +| Monad | 用途 | 示例 | +|-------|------|------| +| `Maybe` | 可能失败的计算 | `safeDiv`、`lookup` | +| `Either` | 带错误信息的计算 | `parseNumber` | +| `IO` | 输入输出操作 | `readFile`、`putStrLn` | +| `List` | 非确定性计算 | 多个结果 | +| `State` | 状态传递 | 计数器、随机数生成 | +| `Reader` | 环境读取 | 配置读取 | +| `Writer` | 日志记录 | 追踪计算过程 | + +## 总结 + +Haskell 的函数式编程范式带来了全新的编程思维方式: + +| 概念 | 核心思想 | 优势 | +|------|---------|------| +| 纯函数 | 无副作用、引用透明 | 可预测、可测试、可并行 | +| 惰性求值 | 按需计算 | 无限数据结构、短路求值 | +| 高阶函数 | 函数作为一等公民 | 强大的组合能力 | +| 类型系统 | 强类型、类型推断 | 编译期捕获错误 | +| Monad | 在纯函数框架内处理副作用 | 优雅的副作用管理 | + +> "学习 Haskell 不是为了在工作中使用它,而是为了以全新的方式思考编程。" —— 匿名 + +Haskell 可能不是最适合所有场景的语言,但它所倡导的函数式编程思想——不可变性、纯函数、组合——已经深刻影响了现代编程语言的设计。从 JavaScript 的 `map`/`filter`/`reduce`,到 Java 的 Stream API,再到 Rust 的迭代器,函数式编程的思想无处不在。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '12 hours', + NOW() - INTERVAL '12 hours', + NOW() - INTERVAL '12 hours' +), +( + 1, + 'Ruby 元编程:打开动态语言的黑箱', + 'ruby-metaprogramming', + 'Ruby 被誉为"程序员的最好朋友",其强大的元编程能力让代码更加灵活和富有表现力。本文深入探讨 define_method、method_missing、类_eval、模块混入等核心元编程技术。', + $doc$ +# Ruby 元编程:打开动态语言的黑箱 + +Ruby 是一门充满魅力的动态语言,Matz(松本行弘)在设计 Ruby 时的理念是:**让编程变得快乐**。这种快乐很大程度上来自于 Ruby 强大的**元编程**(Metaprogramming)能力——即编写能够编写代码的代码。 + +元编程并非 Ruby 独有,但 Ruby 的元编程能力在众多语言中出类拔萃。Rails 框架的成功很大程度上归功于其巧妙运用元编程实现的"约定优于配置"哲学。 + +本文将深入探讨 Ruby 元编程的核心技术,帮助你理解这门动态语言的本质。 + +## 什么是元编程? + +元编程是指在运行时创建、修改或分析代码的技术。在 Ruby 中,几乎所有事物都是对象,包括类本身。这意味着你可以像操作普通对象一样操作类——添加方法、修改方法、甚至在运行时创建全新的类。 + +```ruby +# 类也是对象! +puts String.class # => Class +puts Class.class # => Class +puts Object.class # => Class +``` + +## 动态方法定义 + +### define_method + +`define_method` 允许在运行时动态定义方法: + +```ruby +class Robot + ACTIONS = [:walk, :run, :jump, :fly] + + ACTIONS.each do |action| + define_method(action) do |*args| + speed = args.first || "normal" + "🤖 Robot is #{action}ing at #{speed} speed!" + end + end +end + +robot = Robot.new +puts robot.walk # => 🤖 Robot is walking at normal speed! +puts robot.run("fast") # => 🤖 Robot is running at fast speed! +puts robot.fly("super") # => 🤖 Robot is flying at super speed! +``` + +这种技术在框架开发中极为常见。例如,ActiveRecord 的 `find_by_*` 方法就是动态生成的。 + +### method_missing:拦截不存在的方法 + +`method_missing` 是 Ruby 元编程中最强大也最危险的工具。当调用一个不存在的方法时,Ruby 会将调用转发给 `method_missing`: + +```ruby +class DynamicFinder + def method_missing(name, *args, &block) + if name.to_s.start_with?("find_by_") + attribute = name.to_s.sub("find_by_", "") + "Looking for record where #{attribute} = #{args.first}" + else + super # 如果不是我们处理的格式,交给父类处理 + end + end + + def respond_to_missing?(name, include_private = false) + name.to_s.start_with?("find_by_") || super + end +end + +finder = DynamicFinder.new +puts finder.find_by_name("Alice") # => Looking for record where name = Alice +puts finder.find_by_email("a@b.c") # => Looking for record where email = a@b.c +``` + +### const_missing:动态加载常量 + +```ruby +module AutoLoader + def self.const_missing(name) + file = name.to_s.downcase + require_relative "./#{file}" + const_get(name) + rescue LoadError + super + end +end +``` + +## 打开类(Open Classes) + +Ruby 允许随时重新打开已存在的类并添加或修改方法,这被称为**猴子补丁**(Monkey Patching): + +```ruby +class String + def shout + upcase + "!!!" + end + + def reverse_words + split.reverse.join(" ") + end + + def to_slug + downcase.strip.gsub(/\s+/, '-').gsub(/[^\w-]/, '') + end +end + +puts "hello world".shout # => HELLO WORLD!!! +puts "hello world".reverse_words # => world hello +puts "Hello World!".to_slug # => hello-world +``` + +### 使用 Refinement 安全地扩展 + +为了避免猴子补丁污染全局命名空间,Ruby 2.0 引入了 **Refinement**: + +```ruby +module StringExtensions + refine String do + def shout + upcase + "!!!" + end + end +end + +class MyApp + using StringExtensions + + def self.greet(name) + name.shout + end +end + +puts MyApp.greet("hello") # => HELLO!!! +puts "hello".shout rescue puts "No method error" # => No method error +``` + +## 类_eval 和 instance_eval + +Ruby 提供了多种在特定上下文中执行代码的方式: + +### class_eval(或 module_eval) + +在类的上下文中执行代码,可以访问类的私有方法: + +```ruby +class Person + def initialize(name) + @name = name + end +end + +# 为 Person 类动态添加方法 +Person.class_eval do + attr_reader :name + + def greet + "Hello, I'm #{@name}!" + end + + def self.species + "Homo sapiens" + end +end + +person = Person.new("Alice") +puts person.name # => Alice +puts person.greet # => Hello, I'm Alice! +puts Person.species # => Homo sapiens +``` + +### instance_eval + +在对象的上下文中执行代码: + +```ruby +class Dog + def initialize(name) + @name = name + end +end + +dog = Dog.new("Buddy") + +dog.instance_eval do + def bark + "#{@name} says: Woof!" + end +end + +puts dog.bark # => Buddy says: Woof! +# 注意:bark 方法只存在于这个实例上,其他 Dog 实例没有 +``` + +### instance_exec(带参数) + +```ruby +class Calculator + def initialize(value) + @value = value + end +end + +calc = Calculator.new(10) +result = calc.instance_exec(5) do |n| + @value + n +end +puts result # => 15 +``` + +## 模块与混入(Mixins) + +Ruby 不支持多重继承,但通过模块混入实现了更灵活的功能复用: + +```ruby +module Loggable + def log(message) + puts "[#{Time.now}] #{self.class}: #{message}" + end + + def self.included(base) + puts "#{base} included #{self}" + end +end + +module Validatable + def validate! + raise "Invalid!" unless valid? + end +end + +class User + include Loggable # 实例方法 + extend Validatable # 类方法 + + def valid? + true + end +end + +user = User.new +user.log("Created") # => [2024-...] User: Created +``` + +### prepend:方法前置 + +Ruby 2.0 引入了 `prepend`,可以将模块的方法插入到类的方法链前面: + +```ruby +module Logging + def save + puts "Before save..." + super + puts "After save..." + end +end + +class Article + prepend Logging + + def save + puts "Saving article..." + end +end + +article = Article.new +article.save +# => Before save... +# => Saving article... +# => After save... +``` + +## 元编程在 Rails 中的应用 + +Rails 是 Ruby 元编程能力的最佳展示: + +```ruby +# ActiveRecord 动态属性访问 +user = User.find(1) +user.name # 动态生成 getter +user.name = "Alice" # 动态生成 setter +user.save # 动态生成 SQL + +# 关联宏 +class User < ApplicationRecord + has_many :posts + has_many :comments + belongs_to :company +end + +# 这背后使用元编程动态创建了 posts、comments、company 等方法 +``` + +## 元编程最佳实践 + +| 技术 | 适用场景 | 注意事项 | +|------|---------|---------| +| `define_method` | 批量生成相似方法 | 比 `class_eval` 更清洁 | +| `method_missing` | 实现动态 API | 始终实现 `respond_to_missing?` | +| `class_eval` | 动态修改类 | 会改变类的全局行为 | +| `instance_eval` | DSL 设计 | 注意 `self` 的变化 | +| Refinement | 安全扩展内置类 | 只在 `using` 的作用域内生效 | + +## 总结 + +Ruby 的元编程能力让这门语言充满了表达力和灵活性。通过动态方法定义、方法拦截、类修改和模块混入,你可以写出极其简洁和富有表现力的代码。 + +但元编程也是一把双刃剑: + +- **优点**:代码更 DRY、更表达力、框架更强大 +- **缺点**:调试困难、IDE 支持有限、可读性降低 + +> "Ruby 让编程变得有趣,但元编程让 Ruby 变得强大。" —— 改编自 Matz + +在使用元编程时,请始终牢记:**清晰的代码胜过聪明的代码**。元编程应该用来消除重复、提高抽象层次,而不是用来炫耀技巧。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'draft', + NULL, + NOW(), + NOW() +), +( + 1, + 'Zig 显式内存管理与编译期编程:系统级语言的新选择', + 'zig-memory-management', + 'Zig 是一门注重可读性、健壮性和最优性的系统级编程语言。没有隐式内存分配、没有隐藏的控制流,让每一行代码都清晰可控。', + $doc$ +# Zig 显式内存管理与编译期编程:系统级语言的新选择 + +Zig 是一门相对年轻的系统级编程语言,由 Andrew Kelley 于 2016 年开始开发。它的设计哲学可以概括为:**显式优于隐式**、**可读性优先于奇技淫巧**、**健壮性优先于方便性**。 + +与 C 语言相比,Zig 提供了更好的安全性、更强大的元编程能力和更友好的构建系统。与 Rust 相比,Zig 更加简单直接,没有复杂的所有权系统,而是通过显式的内存分配和错误处理来保证安全。 + +本文将深入探讨 Zig 的核心特性,包括显式内存管理、错误处理、编译期编程和 C 互操作性。 + +## 显式内存分配 + +Zig 最显著的特点之一是**没有隐式内存分配**。在 Zig 中,所有可能分配内存的操作都必须显式地接收一个分配器参数。 + +### 基础内存分配 + +```zig +const std = @import("std"); + +pub fn main() !void { + // 创建通用分配器 + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + // 动态分配内存 + const memory = try allocator.alloc(u8, 100); + defer allocator.free(memory); // 确保内存被释放 + + // 使用内存... + @memcpy(memory, "Hello, Zig!"); + std.debug.print("{s}\n", .{memory}); +} +``` + +### 不同的分配器策略 + +Zig 标准库提供了多种分配器,适用于不同场景: + +| 分配器 | 用途 | 特点 | +|--------|------|------| +| `GeneralPurposeAllocator` | 通用场景 | 安全、支持内存泄漏检测 | +| `PageAllocator` | 大块内存 | 直接向 OS 申请 | +| `FixedBufferAllocator` | 嵌入式/无堆环境 | 使用预分配缓冲区 | +| `ArenaAllocator` | 批量分配/释放 | 一次性释放所有内存 | +| `c_allocator` | C 互操作 | 使用 malloc/free | + +```zig +// 使用 Arena 分配器 +var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); +defer arena.deinit(); +const allocator = arena.allocator(); + +// 分配多个对象 +const str1 = try allocator.dupe(u8, "Hello"); +const str2 = try allocator.dupe(u8, "World"); +// 所有内存会在 arena.deinit() 时一次性释放 +``` + +### 错误处理与内存安全 + +Zig 的 `try` 关键字确保了错误被传播,而 `defer` 确保了资源被释放,即使在错误路径上: + +```zig +fn processData(allocator: std.mem.Allocator, input: []const u8) !void { + const buffer = try allocator.alloc(u8, input.len * 2); + defer allocator.free(buffer); // 无论成功与否都会执行 + + if (input.len == 0) { + return error.EmptyInput; // buffer 仍会被释放 + } + + // 处理数据... + @memcpy(buffer[0..input.len], input); +} +``` + +## 错误处理 + +Zig 的错误处理机制结合了错误联合类型(Error Union Types)和 `try/catch` 风格: + +### 错误联合类型 + +```zig +const FileOpenError = error{ + AccessDenied, + OutOfMemory, + FileNotFound, + NotAFile, +}; + +fn openFile(path: []const u8) FileOpenError!std.fs.File { + return std.fs.cwd().openFile(path, .{}); +} +``` + +`FileOpenError!std.fs.File` 表示这个函数要么返回一个错误(`FileOpenError`),要么返回一个文件(`std.fs.File`)。 + +### try 与 catch + +```zig +pub fn main() !void { + // 使用 try:如果出错,立即返回错误 + const file = try openFile("data.txt"); + defer file.close(); + + // 使用 catch:处理特定错误 + const alt_file = openFile("backup.txt") catch |err| { + std.debug.print("打开备份失败: {}\n", .{err}); + return; + }; + defer alt_file.close(); + + // 使用 catch 提供默认值 + const content = readFile("config.txt") catch "default_config"; +} +``` + +### errdefer + +`errdefer` 只在函数返回错误时执行,非常适合错误路径上的清理: + +```zig +fn createUser(allocator: std.mem.Allocator, name: []const u8) !User { + const user_name = try allocator.dupe(u8, name); + errdefer allocator.free(user_name); // 只有出错时才释放 + + const user_email = try allocator.dupe(u8, "default@example.com"); + errdefer allocator.free(user_email); + + return User{ + .name = user_name, + .email = user_email, + }; +} +``` + +## 编译期编程(Comptime) + +Zig 的 `comptime` 关键字允许在编译期执行代码,这是 Zig 最强大的特性之一: + +### 编译期计算 + +```zig +fn fibonacci(comptime n: u32) u32 { + if (n <= 1) return n; + return fibonacci(n - 1) + fibonacci(n - 2); +} + +const fib_10 = comptime fibonacci(10); // 编译期计算! +``` + +### 类型作为参数 + +```zig +fn Vector(comptime T: type, comptime n: comptime_int) type { + return struct { + data: [n]T, + + pub fn add(self: @This(), other: @This()) @This() { + var result: @This() = undefined; + for (&result.data, self.data, other.data) |*r, a, b| { + r.* = a + b; + } + return result; + } + }; +} + +const Vec3f = Vector(f32, 3); +var v1 = Vec3f{ .data = .{ 1, 2, 3 } }; +var v2 = Vec3f{ .data = .{ 4, 5, 6 } }; +const v3 = v1.add(v2); +``` + +### 编译期反射 + +```zig +fn printStructInfo(comptime T: type) void { + const info = @typeInfo(T); + std.debug.print("Type: {s}\n", .{@typeName(T)}); + + if (info == .Struct) { + std.debug.print("Fields:\n"); + inline for (info.Struct.fields) |field| { + std.debug.print(" {s}: {}\n", .{ field.name, field.type }); + } + } +} +``` + +## C 互操作性 + +Zig 与 C 的互操作性非常出色,可以直接导入 C 头文件并调用 C 函数: + +```zig +const c = @cImport({ + @cInclude("stdio.h"); + @cInclude("stdlib.h"); +}); + +pub fn main() void { + c.printf("Hello from C!\n"); + + const ptr = c.malloc(100); + defer c.free(ptr); + + // Zig 可以直接处理 C 指针 + const zig_slice = @as([*]u8, @ptrCast(ptr))[0..100]; +} +``` + +## Zig 的哲学 + +| 原则 | 说明 | 示例 | +|------|------|------| +| **显式优于隐式** | 没有隐式分配、没有隐式转换 | 所有分配器必须显式传递 | +| **无隐藏控制流** | 没有运算符重载、没有隐式析构 | 资源释放用 `defer` | +| **无隐藏分配** | 内存分配一目了然 | `ArrayList` 需要传入分配器 | +| **可读性优先** | 代码应该像散文一样易读 | 简洁的语法,无冗余符号 | +| **与 C 互操作** | 无缝集成现有 C 代码库 | `@cImport` 直接导入头文件 | + +## 总结 + +Zig 是一门为系统编程而生的现代语言,它吸收了 C 的简洁和 Rust 的安全理念,同时保持了自己的独特风格: + +1. **显式内存管理**:没有垃圾回收,没有隐式分配 +2. **强大的错误处理**:错误联合类型 + try/catch +3. **编译期编程**:`comptime` 提供元编程能力 +4. **出色的 C 互操作**:无缝集成现有生态系统 +5. **简单直接**:没有复杂的所有权系统,只有清晰的规则 + +> **Zig 的设计理念**:"专注于调试你的应用程序而不是调试你的编程语言知识。" + +对于需要高性能、低级别控制、但又不想处理 C 语言种种陷阱的项目,Zig 是一个极具吸引力的选择。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '6 hours', + NOW() - INTERVAL '6 hours', + NOW() - INTERVAL '6 hours' +), +( + 1, + 'Markdown 语法完全测试:渲染引擎兼容性验证', + 'markdown-syntax-test', + '一篇专门用于测试博客系统对 Markdown 各种语法元素支持情况的文章,包含文本格式、列表、代码块、表格、数学公式、脚注等完整测试。', + $doc$ +# Markdown 语法完全测试:渲染引擎兼容性验证 + +这是一篇专门用于测试 Markdown 渲染效果的文章。无论你使用的是 CommonMark、GitHub Flavored Markdown (GFM)、还是其他扩展方言,本文都涵盖了最全面的语法测试用例。 + +Markdown 的设计目标是**易读易写**,其语法灵感来源于纯文本电子邮件的格式。本文不仅展示各种语法元素,还说明了它们的使用场景和最佳实践。 + +## 文本格式 + +Markdown 支持多种内联文本格式: + +这是**粗体**(使用 `**` 或 `__` 包裹),这是*斜体*(使用 `*` 或 `_` 包裹),这是***粗斜体***(同时使用两者),这是~~删除线~~(使用 `~~` 包裹),这是`行内代码`(使用反引号包裹)。 + +### 强调嵌套 + +你可以嵌套不同的格式:这是**粗体中的_斜体_**,这是*斜体中的**粗体***。 + +### 转义字符 + +如果需要显示 Markdown 语法字符本身,可以使用反斜杠转义:\*这不是斜体\*,\`这不是代码\`。 + +## 标题层级 + +Markdown 支持六级标题,使用 `#` 的数量表示层级: + +### 三级标题 + +三级标题常用于文章的主要小节。 + +#### 四级标题 + +四级标题用于更细分的子节。 + +##### 五级标题 + +五级标题用于细节描述。 + +###### 六级标题 + +六级标题是最深层级,一般用于列表项内的标题。 + +## 列表 + +### 无序列表 + +无序列表使用 `-`、`+` 或 `*` 作为标记符: + +- 第一项:这是列表的第一项 +- 第二项:包含子列表 + - 嵌套项 1:使用两个空格缩进 + - 嵌套项 2:可以继续嵌套 + - 更深嵌套:三层缩进 + - 混合使用不同标记符也可以 +- 第三项:回到第一层 + +### 有序列表 + +有序列表使用数字加句点: + +1. 第一步:准备工作 +2. 第二步:执行操作 + 1. 子步骤 A:检查环境 + 2. 子步骤 B:执行命令 +3. 第三步:验证结果 + +### 任务列表 + +GitHub Flavored Markdown 支持任务列表: + +- [x] 已完成任务:设置开发环境 +- [x] 已完成任务:编写核心代码 +- [ ] 未完成任务:编写单元测试 +- [ ] 未完成任务:部署到生产环境 +- [x] 已完成任务:编写文档 + +## 引用块 + +引用块用于引用他人的话或强调重要内容: + +> 这是一段普通的引用块。引用块可以包含多行内容, +> 每一行都以 `>` 开头。 +> +> > 这是嵌套引用块。在引用中再引用,形成层级结构。 +> > 这在回复邮件或讨论中非常常见。 +> +> 回到第一层引用,继续你的论述。 + +### 引用块中的其他元素 + +> **注意**:引用块中可以包含其他 Markdown 元素。 +> +> - 比如列表项 +> - 比如 `行内代码` +> - 比如 [链接](https://example.com) +> +> ```python +> # 甚至可以包含代码块 +> print("在引用块中执行代码") +> ``` + +## 代码块 + +代码块是技术文章的核心。Markdown 支持多种方式展示代码。 + +### Python + +```python +def hello_world(name: str = "World") -> str: + """返回问候语。""" + return f"Hello, {name}!" + +class Greeter: + def __init__(self, greeting: str = "Hello"): + self.greeting = greeting + + def greet(self, name: str) -> str: + return f"{self.greeting}, {name}!" + +if __name__ == "__main__": + greeter = Greeter("Hi") + print(greeter.greet("Alice")) +``` + +### Rust + +```rust +fn main() { + let name = "world"; + println!("Hello, {}!", name); + + let numbers = vec![1, 2, 3, 4, 5]; + let sum: i32 = numbers.iter().sum(); + println!("Sum: {}", sum); +} +``` + +### JavaScript + +```javascript +async function fetchUserData(userId) { + try { + const response = await fetch(`/api/users/${userId}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); + } catch (error) { + console.error("Failed to fetch user:", error); + throw error; + } +} +``` + +### Go + +```go +package main + +import ( + "fmt" + "time" +) + +func worker(id int, jobs <-chan int, results chan<- int) { + for j := range jobs { + fmt.Printf("Worker %d processing job %d\n", id, j) + time.Sleep(time.Second) + results <- j * 2 + } +} + +func main() { + jobs := make(chan int, 100) + results := make(chan int, 100) + + for w := 1; w <= 3; w++ { + go worker(w, jobs, results) + } + + for j := 1; j <= 9; j++ { + jobs <- j + } + close(jobs) + + for a := 1; a <= 9; a++ { + <-results + } +} +``` + +### JSON + +```json +{ + "project": "Yggdrasil", + "version": "1.0.0", + "description": "A modern blog system built with Rust and Dioxus", + "dependencies": { + "frontend": "Dioxus 0.7", + "backend": "tokio-postgres", + "database": "PostgreSQL 15" + }, + "features": [ + "Markdown support", + "Server-side rendering", + "Real-time preview" + ] +} +``` + +### SQL + +```sql +-- 获取已发布的文章列表 +SELECT + p.id, + p.title, + p.slug, + p.summary, + p.published_at, + u.username as author +FROM posts p +JOIN users u ON p.author_id = u.id +WHERE p.status = 'published' + AND p.deleted_at IS NULL +ORDER BY p.published_at DESC +LIMIT 20; +``` + +### Bash + +```bash +#!/bin/bash + +# 脚本:备份数据库 +echo "开始备份..." + +BACKUP_DIR="./backups" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +FILENAME="backup_${TIMESTAMP}.sql" + +mkdir -p "$BACKUP_DIR" +pg_dump "$DATABASE_URL" > "$BACKUP_DIR/$FILENAME" + +if [ $? -eq 0 ]; then + echo "✅ 备份成功: $FILENAME" + ls -lh "$BACKUP_DIR/$FILENAME" +else + echo "❌ 备份失败" + exit 1 +fi +``` + +### 行内代码高亮 + +你也可以在段落中使用行内代码,比如 `git status`、`:wq`、`<Route>`、`<Suspense>`。 + +## 表格 + +Markdown 表格使用管道符 `|` 和连字符 `-` 构建: + +### 基础表格 + +| 语言 | 类型 | 内存管理 | 并发模型 | 适用场景 | +|------|------|----------|----------|---------| +| Rust | 系统级 | 所有权系统 | Fearless Concurrency | 高性能系统、嵌入式 | +| Go | 系统级 | 垃圾回收 | Goroutine + Channel | 云原生、微服务 | +| Python | 高级动态 | 垃圾回收 (GIL) | 多进程/异步IO | 数据科学、Web开发 | +| JavaScript | 高级动态 | 垃圾回收 | 事件循环 + Promise | Web前端、Node.js | +| C++ | 系统级 | 手动/RAII | 线程 + 锁 | 游戏引擎、高频交易 | +| Zig | 系统级 | 显式分配 | 线程 | 系统工具、嵌入式 | + +### 对齐方式 + +| 左对齐 | 居中对齐 | 右对齐 | +|:-------|:-------:|-------:| +| 内容 1 | 内容 2 | 内容 3 | +| A | B | 100 | +| 长文本示例 | 居中显示 | 999.99 | + +## 水平线 + +水平线用于分隔文章的不同部分: + +--- + +*** + +___ + +## 链接 + +Markdown 支持多种链接格式: + +### 行内链接 + +[GitHub](https://github.com) - 世界上最流行的代码托管平台。 + +[相对链接](/) - 链接到网站首页。 + +[带标题的链接](https://example.com "示例网站") - 鼠标悬停显示标题。 + +### 引用式链接 + +[Google][google-link] 和 [Bing][bing-link] 是两大搜索引擎。 + +[google-link]: https://google.com "Google 搜索" +[bing-link]: https://bing.com "Bing 搜索" + +## 图片 + +![Markdown Logo](https://markdown-here.com/img/icon256.png) + +## HTML 内嵌 + +某些场景下,你可能需要直接使用 HTML: + +
+

提示: 这是使用 HTML 创建的自定义样式块。当 Markdown 的表达能力不足时,可以直接嵌入 HTML。但请注意,这会降低内容的可移植性。

+
+ +## 特殊字符 + +Markdown 和 HTML 实体: + +- 版权符号:© 2024 Yggdrasil +- 注册商标:Markdown® +- 商标符号:GitHub™ +- 长破折号:这是—一个长破折号 +- 短破折号:这是–一个短破折号 +- 省略号:等等… + +## 脚注 + +脚注是学术写作中常用的功能[^1]。你可以在同一条注释中引用多个脚注[^2][^3]。 + +[^1]: 这是第一个脚注的内容。脚注可以包含多行文字和格式。 +[^2]: 脚注通常用于提供补充说明或引用来源。 +[^3]: Markdown 的脚注语法在不同的渲染引擎中支持程度不同。 + +## 总结 + +本文测试了 Markdown 的完整语法集: + +| 语法元素 | 支持情况 | 说明 | +|---------|---------|------| +| 文本格式 | ✅ | 粗体、斜体、删除线、行内代码 | +| 标题 | ✅ | 六级标题支持 | +| 列表 | ✅ | 无序、有序、嵌套、任务列表 | +| 引用块 | ✅ | 支持嵌套引用 | +| 代码块 | ✅ | 语法高亮 | +| 表格 | ✅ | 对齐方式 | +| 水平线 | ✅ | 多种分隔符 | +| 链接 | ✅ | 行内和引用式 | +| 图片 | ✅ | 外部图片 | +| HTML | ✅ | 内嵌 HTML 块 | +| 脚注 | ⚠️ | 依赖渲染引擎支持 | + +如果你的渲染引擎正确显示了以上内容,说明它对 Markdown 的支持非常完善! + +--- + +*本文用于测试 Markdown 渲染引擎的兼容性。* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '3 hours', + NOW() - INTERVAL '3 hours', + NOW() - INTERVAL '3 hours' +), +( + 1, + 'Rust 错误处理完全指南:Result、Option 与 thiserror', + 'rust-error-handling', + 'Rust 使用 Result 和 Option 类型来处理可能失败的操作和可能缺失的值,取代了传统的异常机制。本文深入讲解 Rust 的错误处理哲学和最佳实践。', + $doc$ +# Rust 错误处理完全指南:Result、Option 与 thiserror + +Rust 没有异常机制(Exception)。对于习惯了 Java、Python 或 JavaScript 的开发者来说,这可能需要一些适应。但 Rust 的类型驱动错误处理——通过 `Result` 和 `Option` 类型——实际上是一种更健壮、更明确的设计。 + +本文将深入探讨 Rust 的错误处理哲学,从基础的 `Option` 和 `Result` 到自定义错误类型,再到 `?` 操作符和第三方库 `thiserror`。 + +## 为什么 Rust 没有异常? + +传统的异常机制存在几个问题: + +1. **不可见的控制流**:异常可以穿透任意层级的调用栈,使得代码路径难以追踪 +2. **类型安全缺失**:编译器无法检查你是否处理了所有可能的异常 +3. **性能开销**:异常处理通常需要运行时维护额外的栈信息 + +Rust 的解决方案是**将错误作为值返回**。如果一个函数可能失败,它的返回类型就明确地表示这一点。编译器会强迫你处理这个错误,否则代码无法通过编译。 + +## Option 类型:处理可能缺失的值 + +`Option` 是 Rust 中表示"可能有值,也可能没有"的类型: + +```rust +enum Option { + Some(T), // 有值 + None, // 无值 +} +``` + +### 基础使用 + +```rust +fn find_char(s: &str, c: char) -> Option { + s.find(c) // 如果找到返回 Some(index),否则返回 None +} + +fn main() { + // 使用 match 处理 Option + match find_char("hello", 'e') { + Some(index) => println!("✅ 找到字符在位置 {}", index), + None => println!("❌ 未找到字符"), + } + + // 使用 if let 简化(只关心 Some 的情况) + if let Some(index) = find_char("hello", 'l') { + println!("🎯 在位置 {}", index); + } + + // while let:循环处理 + let mut text = "hello"; + while let Some(c) = text.chars().next() { + println!("字符: {}", c); + text = &text[1..]; + } +} +``` + +### Option 的组合子 + +`Option` 提供了丰富的组合子方法来链式处理: + +```rust +fn main() { + let maybe_number: Option = Some(5); + + // map:对 Some 中的值进行转换 + let doubled = maybe_number.map(|n| n * 2); // Some(10) + + // and_then:链式调用返回 Option 的函数 + let result = maybe_number + .and_then(|n| if n > 3 { Some(n) } else { None }) + .map(|n| n * 2); + + // unwrap_or:提供默认值 + let value = maybe_number.unwrap_or(0); // 5 + let empty = None::.unwrap_or(0); // 0 + + // unwrap_or_else:懒加载默认值 + let lazy = None::.unwrap_or_else(|| expensive_computation()); + + // ok_or:将 Option 转换为 Result + let result: Result = maybe_number.ok_or("值为空"); +} +``` + +## Result 类型:处理可能失败的操作 + +`Result` 是 Rust 中表示操作可能失败的核心类型: + +```rust +enum Result { + Ok(T), // 成功 + Err(E), // 失败,携带错误信息 +} +``` + +### 基础使用 + +```rust +use std::fs::File; +use std::io::{self, Read}; + +fn read_username_from_file() -> Result { + let mut file = File::open("hello.txt")?; + let mut username = String::new(); + file.read_to_string(&mut username)?; + Ok(username) +} +``` + +### ? 操作符:错误传播 + +`?` 操作符是 Rust 错误处理中最常用的语法糖。它在 `Result` 为 `Ok` 时解包值,在 `Err` 时提前返回错误: + +```rust +fn read_config() -> Result { + let content = std::fs::read_to_string("config.json")?; // 如果失败,直接返回 Err + let config: Config = serde_json::from_str(&content)?; // 如果失败,直接返回 Err + Ok(config) +} +``` + +`?` 操作符的强大之处在于它可以自动进行错误类型的转换。只要当前函数的返回错误类型实现了 `From`,就可以使用 `?`。 + +### Result 的组合子 + +与 `Option` 类似,`Result` 也有丰富的组合子: + +```rust +fn main() { + let result: Result = Ok(42); + + // map:对 Ok 中的值进行转换 + let doubled = result.map(|n| n * 2); // Ok(84) + + // map_err:对 Err 中的值进行转换 + let with_prefix = Err("error").map_err(|e| format!("ERROR: {}", e)); + + // and_then:链式调用 + let chained = Ok(5) + .and_then(|n| if n > 0 { Ok(n * 2) } else { Err("负数") }); + + // unwrap_or / unwrap_or_else + let value = result.unwrap_or(0); // 42 + + // expect:unwrap 的变体,可自定义 panic 消息 + let file = std::fs::read_to_string("config.txt") + .expect("config.txt 必须存在"); +} +``` + +## 自定义错误类型 + +在实际项目中,你可能需要定义自己的错误类型。Rust 提供了多种方式: + +### 枚举错误类型 + +```rust +#[derive(Debug)] +pub enum AppError { + Io(std::io::Error), + Parse(std::num::ParseIntError), + InvalidInput(String), + NotFound { resource: String, id: i64 }, +} + +impl std::fmt::Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + AppError::Io(e) => write!(f, "IO 错误: {}", e), + AppError::Parse(e) => write!(f, "解析错误: {}", e), + AppError::InvalidInput(msg) => write!(f, "无效输入: {}", msg), + AppError::NotFound { resource, id } => write!(f, "{} 不存在: id={}", resource, id), + } + } +} + +impl std::error::Error for AppError {} + +// 实现 From trait 以支持 ? 操作符 +impl From for AppError { + fn from(err: std::io::Error) -> Self { + AppError::Io(err) + } +} + +impl From for AppError { + fn from(err: std::num::ParseIntError) -> Self { + AppError::Parse(err) + } +} +``` + +### 使用 thiserror 简化 + +手动实现错误类型很繁琐。[`thiserror`](https://docs.rs/thiserror) 是一个宏库,可以大大简化这个过程: + +```rust +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum AppError { + #[error("IO 错误: {0}")] + Io(#[from] std::io::Error), + + #[error("解析错误: {0}")] + Parse(#[from] std::num::ParseIntError), + + #[error("无效输入: {0}")] + InvalidInput(String), + + #[error("{resource} 不存在: id={id}")] + NotFound { resource: String, id: i64 }, + + #[error("数据库错误")] + Database { + #[from] + source: sqlx::Error, + }, +} + +// 使用 +fn load_user(id: i64) -> Result { + let content = std::fs::read_to_string("users.json")?; // 自动转换 io::Error + let users: Vec = serde_json::from_str(&content)?; + + users.into_iter() + .find(|u| u.id == id) + .ok_or_else(|| AppError::NotFound { + resource: "User".to_string(), + id, + }) +} +``` + +### anyhow:快速原型开发 + +如果你不需要精细的错误分类,可以使用 [`anyhow`](https://docs.rs/anyhow) 库: + +```rust +use anyhow::{Context, Result}; + +fn main() -> Result<()> { + let config = std::fs::read_to_string("config.toml") + .with_context(|| "无法读取配置文件")?; + + let settings: Settings = toml::from_str(&config) + .context("配置文件格式错误")?; + + println!("{:?}", settings); + Ok(()) +} +``` + +## Option 与 Result 的转换 + +在实际代码中,经常需要在 `Option` 和 `Result` 之间转换: + +```rust +// Option -> Result +let opt: Option = Some(5); +let res: Result = opt.ok_or("值为空"); // Ok(5) +let res2 = opt.ok_or_else(|| format!("{} 为空", "value")); // Ok(5) + +// Result -> Option +let res: Result = Ok(5); +let opt = res.ok(); // Some(5) +let err_opt = res.err(); // None + +// 在迭代中收集 Result +let numbers = vec!["1", "2", "3", "not_a_number"]; +let parsed: Result, _> = numbers.iter() + .map(|s| s.parse::()) + .collect(); // 如果有任何 Err,返回第一个 Err +``` + +## 错误处理最佳实践 + +| 场景 | 推荐方式 | 示例 | +|------|---------|------| +| 快速原型 | `anyhow::Result` | `fn main() -> anyhow::Result<()>` | +| 库开发 | `thiserror` | 定义精细的错误枚举 | +| 错误传播 | `?` 操作符 | `let x = may_fail()?;` | +| 提供默认值 | `unwrap_or` | `let x = opt.unwrap_or(0);` | +| 不可恢复错误 | `expect` | `let x = vec[0].expect("不能为空")` | +| 可选值链 | `and_then` + `map` | `opt.and_then(f).map(g)` | + +## 总结 + +Rust 的错误处理机制强迫开发者显式处理所有错误路径,这看似繁琐,实际上带来了巨大的好处: + +| 类型 | 表示 | 使用场景 | +|------|------|---------| +| `Option` | `Some(T)` / `None` | 可能缺失的值 | +| `Result` | `Ok(T)` / `Err(E)` | 可能失败的操作 | +| `?` 操作符 | 自动传播错误 | 函数内部错误处理 | +| `thiserror` | 宏生成的错误类型 | 库开发 | +| `anyhow` | 泛型错误类型 | 应用程序开发 | + +Rust 的类型驱动错误处理是一种**以编译期检查换取运行时安全**的设计哲学。虽然需要写更多的错误处理代码,但换来的是程序在运行时几乎不会因为未处理的错误而崩溃。 + +> "在 Rust 中,如果代码通过了编译,那么它已经处理了所有可能的错误路径。" —— 这是 Rust 给予开发者的最大信心。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'published', + NOW() - INTERVAL '2 hours', + NOW() - INTERVAL '2 hours', + NOW() - INTERVAL '2 hours' +), +( + 1, + 'Python 数据类完全指南:dataclass、attrs 与 Pydantic', + 'python-dataclasses', + 'Python 3.7 引入的 dataclass 装饰器大大简化了类的定义。本文深入对比 dataclass、attrs 和 Pydantic,帮助你选择最合适的方案。', + $doc$ +# Python 数据类完全指南:dataclass、attrs 与 Pydantic + +在 Python 中定义一个简单的数据容器类,传统方式需要编写大量样板代码:`__init__`、`__repr__`、`__eq__`……这些代码不仅冗余,而且容易出错。Python 3.7 引入的 **`dataclass`** 装饰器彻底解决了这个问题,让你可以用声明式的方式定义数据类。 + +本文将深入介绍 `dataclass`、`attrs` 和 `Pydantic` 三种数据类方案,帮助你根据场景选择最合适的工具。 + +## dataclass(标准库) + +`dataclasses` 模块是 Python 3.7+ 的标准库,无需安装任何第三方包。 + +### 基础用法 + +```python +from dataclasses import dataclass, field +from typing import List, Optional + +@dataclass +class Person: + name: str + age: int = 0 + email: Optional[str] = None + hobbies: List[str] = field(default_factory=list) + + def greet(self) -> str: + return f"Hello, I'm {self.name}!" + + def is_adult(self) -> bool: + return self.age >= 18 + +# 使用 +person = Person("Alice", 30, "alice@example.com", ["reading", "coding"]) +print(person) +# Person(name='Alice', age=30, email='alice@example.com', hobbies=['reading', 'coding']) + +print(person.greet()) # Hello, I'm Alice! +print(person == Person("Alice", 30)) # False(因为 email 和 hobbies 不同) +``` + +`@dataclass` 装饰器自动为你生成了 `__init__`、`__repr__`、`__eq__` 等方法。 + +### dataclass 参数 + +```python +@dataclass(init=True, # 生成 __init__ + repr=True, # 生成 __repr__ + eq=True, # 生成 __eq__ + order=False, # 生成比较方法 (__lt__, __le__, __gt__, __ge__) + unsafe_hash=False, # 生成 __hash__ + frozen=False, # 不可变实例 + slots=False, # 使用 __slots__ 节省内存 + kw_only=False) # 关键字-only 参数 +class Product: + name: str + price: float + quantity: int = 0 +``` + +### field() 函数 + +`field()` 提供了更精细的控制: + +```python +from dataclasses import dataclass, field + +@dataclass +class User: + id: int = field(init=False) # 不通过 __init__ 传入 + name: str + created_at: str = field(default_factory=lambda: datetime.now().isoformat()) + password: str = field(repr=False) # 不在 __repr__ 中显示 + + def __post_init__(self): + self.id = hash(self.name) % 10000 + +user = User("alice", password="secret123") +print(user) +# User(id=1234, name='alice', created_at='2024-...') +# 注意:password 不在 repr 中 +``` + +### frozen dataclass(不可变数据类) + +```python +from dataclasses import dataclass + +@dataclass(frozen=True) +class Point: + x: float + y: float + + def distance_from_origin(self) -> float: + return (self.x ** 2 + self.y ** 2) ** 0.5 + +p = Point(3.0, 4.0) +print(p.distance_from_origin()) # 5.0 +# p.x = 5.0 # FrozenInstanceError: cannot assign to field 'x' +``` + +不可变数据类天然是线程安全的,可以作为字典的键使用。 + +### 继承 + +```python +@dataclass +class Employee(Person): + employee_id: str + department: str = "Engineering" + salary: float = field(default=0.0, repr=False) + +emp = Employee("Bob", 25, None, [], "E001", "Product") +print(emp) +``` + +## attrs(第三方库) + +[`attrs`](https://www.attrs.org/) 是比 `dataclass` 更早出现的第三方库,功能更强大。 + +```python +import attr + +@attr.s(auto_attribs=True) +class Vehicle: + wheels: int = 4 + color: str = "red" + brand: str = attr.ib(default="Unknown", validator=attr.validators.instance_of(str)) + + def describe(self) -> str: + return f"A {self.color} {self.brand} with {self.wheels} wheels" + +v = Vehicle(wheels=2, color="blue", brand="Yamaha") +print(v.describe()) # A blue Yamaha with 2 wheels +``` + +### attrs 的优势 + +| 特性 | dataclass | attrs | 说明 | +|------|-----------|-------|------| +| 标准库 | ✅ | ❌ | dataclass 无需安装 | +| 验证器 | 有限 | 强大 | attrs 内置多种验证器 | +| 转换器 | 无 | 有 | attrs 支持类型转换 | +| 性能 | 好 | 更好 | attrs 生成的代码更优化 | +| 序列化 | 需手动 | 内置 | attrs 支持多种格式 | +| 元数据 | 有限 | 丰富 | attrs 的 metadata 系统 | + +### attrs 验证器示例 + +```python +import attr + +@attr.s +class User: + name: str = attr.ib(validator=attr.validators.instance_of(str)) + age: int = attr.ib( + validator=[ + attr.validators.instance_of(int), + attr.validators.ge(0), # 大于等于 0 + attr.validators.le(150) # 小于等于 150 + ] + ) + email: str = attr.ib( + validator=attr.validators.matches_re(r"^[\w\.-]+@[\w\.-]+\.\w+$") + ) + +# 使用 +try: + user = User("Alice", -5, "alice@example.com") +except ValueError as e: + print(f"验证失败: {e}") +``` + +## Pydantic:数据验证与序列化 + +[`Pydantic`](https://docs.pydantic.dev/) 是目前 Python 生态系统中最流行的数据验证库,基于类型提示自动进行数据验证和转换。 + +### 基础用法 + +```python +from pydantic import BaseModel, Field, EmailStr, validator +from typing import List, Optional +from datetime import datetime + +class User(BaseModel): + id: int + name: str = Field(..., min_length=1, max_length=100) + email: EmailStr # 自动验证邮箱格式 + age: int = Field(..., ge=0, le=150) + is_active: bool = True + tags: List[str] = [] + created_at: Optional[datetime] = None + + @validator('name') + def name_must_not_be_empty(cls, v): + if not v.strip(): + raise ValueError('Name must not be empty') + return v.strip() + +# 自动验证和转换 +user = User(id=1, name="Alice", email="alice@example.com", age=30) +print(user) +# id=1 name='Alice' email='alice@example.com' age=30 is_active=True tags=[] created_at=None + +# 从字典创建(自动转换类型) +user_dict = {"id": "2", "name": "Bob", "email": "bob@test.com", "age": "25"} +user2 = User(**user_dict) +print(user2.age) # 25 (自动从 str 转换为 int) + +# JSON 序列化 +print(user.json()) +# {"id": 1, "name": "Alice", "email": "alice@example.com", ...} +``` + +### Pydantic 配置 + +```python +from pydantic import BaseModel, Field + +class Config: + """Pydantic 配置选项""" + pass + +class User(BaseModel): + class Config: + # 允许从 ORM 对象创建 + orm_mode = True + # 字段别名(如 JSON 中的 snake_case 映射到 camelCase) + alias_generator = lambda x: x.lower() + # 验证赋值 + validate_assignment = True + # 错误信息使用中文 + error_msg_templates = { + 'value_error.missing': '字段必填', + } + + name: str + age: int +``` + +## 三者对比 + +| 特性 | dataclass | attrs | Pydantic | +|------|-----------|-------|----------| +| 安装 | 标准库 | `pip install attrs` | `pip install pydantic` | +| 类型转换 | ❌ | 部分 | ✅ 自动 | +| 数据验证 | ❌ | ✅ 内置 | ✅ 强大 | +| JSON 序列化 | ❌ | ✅ | ✅ 内置 | +| ORM 集成 | ❌ | ❌ | ✅ SQLAlchemy | +| 性能 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | +| 学习曲线 | 低 | 中 | 低 | +| 适用场景 | 简单数据结构 | 复杂验证逻辑 | API 开发、配置管理 | + +## 如何选择? + +1. **简单数据类**:使用 `dataclass`(标准库,零依赖) +2. **需要复杂验证**:使用 `attrs`(验证器、转换器更强大) +3. **Web/API 开发**:使用 `Pydantic`(自动验证、JSON 序列化、FastAPI 原生支持) +4. **配置管理**:使用 `Pydantic`(环境变量加载、类型转换) +5. **ORM 模型**:使用 `Pydantic`(`orm_mode` 支持) + +## 总结 + +Python 的数据类生态系统已经非常成熟: + +- **`dataclass`**:简单、标准库、适合大多数场景 +- **`attrs`**:功能强大、验证丰富、适合复杂业务逻辑 +- **`Pydantic`**:验证 + 序列化 + ORM、Web 开发首选 + +三者并非互斥。你可以在项目中根据场景灵活选择。例如,内部数据模型使用 `dataclass`,API 接口使用 `Pydantic`,核心业务实体使用 `attrs`。 + +> **最佳实践**:不要过度设计。如果 `dataclass` 能满足需求,就不要引入额外的依赖。 + +--- + +*本文首发于 Yggdrasil 博客* +$doc$, + NULL, + 'draft', + NULL, + NOW(), + NOW() +), +( + 1, + 'C 语言指针详解', + 'c-pointers-explained', + '深入理解 C 语言指针的本质,从基础概念到高级用法,全面掌握指针与数组、函数指针、多级指针、内存管理等核心技术,避免常见的指针陷阱。', + $doc$ +# C 语言指针详解:从基础到精通 + +指针是 C 语言最核心的特性之一,也是最令初学者困惑的概念。理解指针的本质,是掌握 C 语言的关键一步。本文将从基础概念出发,逐步深入探讨指针的各种高级用法和常见陷阱。 + +## 指针的本质 + +### 什么是指针 + +指针是一个变量,其值为另一个变量的内存地址。每个变量在内存中都有一个唯一的地址,指针就是存储这个地址的特殊变量。 + +```c +#include + +int main() { + int num = 42; + int *ptr = # + + printf("Value of num: %d\n", num); + printf("Address of num: %p\n", (void*)&num); + printf("Value of ptr (address): %p\n", (void*)ptr); + printf("Value pointed by ptr: %d\n", *ptr); + + return 0; +} +``` + +在这个例子中: + +| 表达式 | 含义 | +|--------|------| +| `&num` | 取变量 num 的地址 | +| `int *ptr` | 声明一个指向 int 的指针 | +| `*ptr` | 解引用,获取指针指向的值 | +| `ptr = &num` | 将 num 的地址赋给指针 | + +### 指针的大小 + +指针的大小取决于系统的架构,而不是指向的数据类型: + +```c +#include + +int main() { + int *ip; + char *cp; + double *dp; + void *vp; + + printf("sizeof(int*): %zu bytes\n", sizeof(ip)); + printf("sizeof(char*): %zu bytes\n", sizeof(cp)); + printf("sizeof(double*):%zu bytes\n", sizeof(dp)); + printf("sizeof(void*): %zu bytes\n", sizeof(vp)); + + return 0; +} +``` + +在 64 位系统中,所有指针通常都是 8 字节(64 位)。 + +## 指针与数组 + +### 数组名的本质 + +数组名在大多数表达式中会退化为指向数组首元素的指针: + +```c +#include + +int main() { + int arr[5] = {10, 20, 30, 40, 50}; + int *ptr = arr; // 等价于 &arr[0] + + // 以下四种写法等价 + printf("arr[2] = %d\n", arr[2]); + printf("ptr[2] = %d\n", ptr[2]); + printf("*(arr + 2) = %d\n", *(arr + 2)); + printf("*(ptr + 2) = %d\n", *(ptr + 2)); + + return 0; +} +``` + +### 指针算术 + +指针算术会自动考虑数据类型的大小: + +```c +#include + +int main() { + int arr[5] = {10, 20, 30, 40, 50}; + int *ptr = arr; + + printf("ptr = %p\n", (void*)ptr); + printf("ptr + 1 = %p\n", (void*)(ptr + 1)); + printf("ptr + 2 = %p\n", (void*)(ptr + 2)); + + // 差值计算的是元素个数,不是字节数 + printf("(ptr + 3) - ptr = %ld\n", (ptr + 3) - ptr); + + return 0; +} +``` + +> **注意**:指针算术只在指向数组元素时才有意义。对非数组对象的指针进行算术运算会导致未定义行为。 + +## 多级指针 + +### 指向指针的指针 + +```c +#include + +int main() { + int num = 42; + int *ptr1 = # + int **ptr2 = &ptr1; + int ***ptr3 = &ptr2; + + printf("num = %d\n", num); + printf("*ptr1 = %d\n", *ptr1); + printf("**ptr2 = %d\n", **ptr2); + printf("***ptr3 = %d\n", ***ptr3); + + // 修改值 + ***ptr3 = 100; + printf("After modification: num = %d\n", num); + + return 0; +} +``` + +多级指针常用于动态二维数组和函数参数传递: + +```c +#include +#include + +// 使用二级指针创建动态二维数组 +int** create_matrix(int rows, int cols) { + int **matrix = malloc(rows * sizeof(int*)); + for (int i = 0; i < rows; i++) { + matrix[i] = malloc(cols * sizeof(int)); + } + return matrix; +} + +void free_matrix(int **matrix, int rows) { + for (int i = 0; i < rows; i++) { + free(matrix[i]); + } + free(matrix); +} +``` + +## 函数指针 + +### 基本语法 + +函数指针允许我们将函数作为参数传递,实现回调机制: + +```c +#include + +// 函数指针类型定义 +typedef int (*CompareFunc)(int, int); + +int add(int a, int b) { return a + b; } +int subtract(int a, int b) { return a - b; } +int multiply(int a, int b) { return a * b; } + +// 使用函数指针作为参数 +int operate(int a, int b, CompareFunc op) { + return op(a, b); +} + +int main() { + printf("add: %d\n", operate(10, 5, add)); + printf("subtract: %d\n", operate(10, 5, subtract)); + printf("multiply: %d\n", operate(10, 5, multiply)); + + return 0; +} +``` + +### 回调函数应用 + +```c +#include + +// 通用排序函数,使用回调比较 +typedef int (*CompareFunc)(const void*, const void*); + +void bubble_sort(void *arr, size_t n, size_t size, CompareFunc cmp) { + char *base = arr; + char temp[size]; + + for (size_t i = 0; i < n - 1; i++) { + for (size_t j = 0; j < n - i - 1; j++) { + if (cmp(base + j * size, base + (j + 1) * size) > 0) { + // 交换 + memcpy(temp, base + j * size, size); + memcpy(base + j * size, base + (j + 1) * size, size); + memcpy(base + (j + 1) * size, temp, size); + } + } + } +} + +int int_cmp(const void *a, const void *b) { + return (*(int*)a - *(int*)b); +} + +int main() { + int arr[] = {64, 34, 25, 12, 22, 11, 90}; + size_t n = sizeof(arr) / sizeof(arr[0]); + + bubble_sort(arr, n, sizeof(int), int_cmp); + + printf("Sorted array: "); + for (size_t i = 0; i < n; i++) { + printf("%d ", arr[i]); + } + printf("\n"); + + return 0; +} +``` + +## void 指针 + +`void*` 是一种通用指针类型,可以指向任何数据类型: + +```c +#include +#include +#include + +// 通用的内存拷贝函数 +void* my_memcpy(void *dest, const void *src, size_t n) { + char *d = dest; + const char *s = src; + while (n--) { + *d++ = *s++; + } + return dest; +} + +// 泛型交换函数 +void swap(void *a, void *b, size_t size) { + char temp[size]; + memcpy(temp, a, size); + memcpy(a, b, size); + memcpy(b, temp, size); +} + +int main() { + int x = 10, y = 20; + swap(&x, &y, sizeof(int)); + printf("x = %d, y = %d\n", x, y); + + double a = 1.5, b = 2.5; + swap(&a, &b, sizeof(double)); + printf("a = %.1f, b = %.1f\n", a, b); + + return 0; +} +``` + +## const 与指针 + +`const` 与指针的组合有四种情况,每种含义不同: + +| 声明 | 读法 | 含义 | +|------|------|------| +| `int* ptr` | 指向 int 的指针 | 指针和值都可变 | +| `const int* ptr` | 指向常量的指针 | 值不可变,指针可变 | +| `int* const ptr` | 常量指针 | 值可变,指针不可变 | +| `const int* const ptr` | 指向常量的常量指针 | 都不可变 | + +```c +#include + +int main() { + int a = 10, b = 20; + + // 1. 指向常量的指针(常量指针) + const int *ptr1 = &a; + // *ptr1 = 30; // 错误!不能通过 ptr1 修改值 + ptr1 = &b; // 正确,可以修改指针指向 + + // 2. 常量指针 + int *const ptr2 = &a; + *ptr2 = 30; // 正确,可以修改值 + // ptr2 = &b; // 错误!不能修改指针指向 + + // 3. 指向常量的常量指针 + const int *const ptr3 = &a; + // *ptr3 = 40; // 错误! + // ptr3 = &b; // 错误! + + printf("a = %d\n", a); + + return 0; +} +``` + +## 内存布局与对齐 + +```c +#include + +struct Example { + char c; + int i; + char d; +}; + +struct PackedExample { + char c; + int i; + char d; +} __attribute__((packed)); + +int main() { + printf("sizeof(Example): %zu\n", sizeof(struct Example)); + printf("sizeof(PackedExample): %zu\n", sizeof(struct PackedExample)); + + struct Example ex; + printf("Address of c: %p\n", (void*)&ex.c); + printf("Address of i: %p\n", (void*)&ex.i); + printf("Address of d: %p\n", (void*)&ex.d); + + return 0; +} +``` + +## 常见指针陷阱 + +### 1. 未初始化的指针 + +```c +int *ptr; // 野指针,指向随机地址 +*ptr = 10; // 未定义行为!可能导致程序崩溃 +``` + +### 2. 内存泄漏 + +```c +void leak_example() { + int *ptr = malloc(sizeof(int) * 100); + // ... 使用 ptr + // 忘记 free(ptr) —— 内存泄漏! +} +``` + +### 3. 悬空指针 + +```c +int* dangling_pointer() { + int local = 42; + return &local; // 危险!返回局部变量的地址 +} +``` + +### 4. 数组越界 + +```c +int arr[5] = {1, 2, 3, 4, 5}; +int *ptr = arr; +ptr[5] = 10; // 越界访问!未定义行为 +``` + +## 总结 + +指针是 C 语言强大而灵活的工具,掌握指针需要理解: + +1. **内存模型**:理解变量在内存中的存储方式 +2. **类型系统**:指针类型决定了指针算术的步长 +3. **生命周期**:确保指针始终指向有效的内存 +4. **所有权**:谁分配、谁释放,避免内存泄漏 + +> "C 语言让你可以做出所有你想要的错误,包括那些你可能没想到的。" —— 对指针最好的描述 + +通过本文的学习,你应该能够自信地使用指针,并避免常见的陷阱。记住,**理解指针的本质是理解内存**,这是成为优秀 C 程序员的关键。 +$doc$, + NULL, + 'published', + NOW() - INTERVAL '1 hour', + NOW() - INTERVAL '1 hour', + NOW() - INTERVAL '1 hour' +), +( + 1, + 'Elixir 并发与容错', + 'elixir-concurrency', + '深入探索 Elixir 的并发模型和容错机制,从 OTP 进程到 Supervisor 监督树,理解 Actor 模型和 Let it crash 哲学,构建高可用的分布式系统。', + $doc$ +# Elixir 并发与容错:构建高可用系统 + +Elixir 构建在 Erlang VM(BEAM)之上,继承了 Erlang 强大的并发和容错能力。本文将深入探讨 Elixir 的并发模型、进程隔离、Supervisor 监督机制以及分布式系统的构建方法。 + +## 为什么选择 Elixir? + +Elixir 不是另一种 Web 框架,而是一种全新的编程范式。它基于 **Actor 模型**,提供了轻量级进程和消息传递机制,让并发编程变得简单而安全。 + +> WhatsApp 使用 Erlang/OTP 处理每天超过 650 亿条消息,每台服务器维护超过 200 万个并发连接。这就是 BEAM 虚拟机的实力。 + +## Elixir 进程模型 + +### 轻量级进程 + +在 Elixir 中,"进程"不是操作系统进程,而是由 BEAM 虚拟机管理的轻量级执行单元: + +```elixir +# 创建一个新进程 +pid = spawn(fn -> + IO.puts("Hello from a new process!") + IO.puts("Process PID: #{inspect(self())}") +end) + +IO.puts("Main process PID: #{inspect(self())}") +IO.puts("Spawned process PID: #{inspect(pid)}") +``` + +特点对比: + +| 特性 | OS 进程 | Elixir 进程 | +|------|---------|-------------| +| 内存占用 | MB 级别 | ~300 字节 | +| 创建时间 | 毫秒级 | 微秒级 | +| 最大数量 | 数千 | 数百万 | +| 通信方式 | 共享内存 | 消息传递 | +| 调度 | 抢占式 | 协作式 | + +### 进程隔离 + +每个 Elixir 进程都有自己独立的内存空间,进程之间不共享状态: + +```elixir +defmodule Counter do + def start(initial_value \\ 0) do + spawn(fn -> loop(initial_value) end) + end + + defp loop(current_value) do + receive do + {:get, caller} -> + send(caller, {:value, current_value}) + loop(current_value) + + {:increment} -> + loop(current_value + 1) + + {:decrement} -> + loop(current_value - 1) + + {:add, amount} -> + loop(current_value + amount) + end + end +end + +# 使用示例 +counter = Counter.start(10) + +send(counter, {:increment}) +send(counter, {:add, 5}) +send(counter, {:get, self()}) + +receive do + {:value, value} -> IO.puts("Current value: #{value}") +after + 1000 -> IO.puts("Timeout!") +end +``` + +## 消息传递机制 + +### 异步消息 + +Elixir 进程通过异步消息传递进行通信: + +```elixir +defmodule Messenger do + def send_message(to_pid, message) do + send(to_pid, {self(), message}) + end + + def wait_for_reply(timeout \\\\ 5000) do + receive do + {_from, reply} -> {:ok, reply} + after + timeout -> {:error, :timeout} + end + end +end + +# 创建接收进程 +receiver = spawn(fn -> + receive do + {from, msg} -> + IO.puts("Received: #{msg}") + send(from, {self(), "Reply: #{msg}"}) + end +end) + +Messenger.send_message(receiver, "Hello Elixir!") +case Messenger.wait_for_reply() do + {:ok, reply} -> IO.puts("Got reply: #{reply}") + {:error, reason} -> IO.puts("Failed: #{reason}") +end +``` + +### 消息邮箱 + +每个进程都有一个消息邮箱,消息按到达顺序排队: + +```elixir +defmodule MessageProcessor do + def process_messages do + receive do + message -> + IO.puts("Processing: #{inspect(message)}") + process_messages() + end + end +end + +processor = spawn(&MessageProcessor.process_messages/0) + +send(processor, :first) +send(processor, :second) +send(processor, {:complex, [1, 2, 3]}) +``` + +## GenServer:状态机模式 + +GenServer 是 OTP 行为(Behaviour)之一,提供了一种标准化的方式来构建有状态的服务器进程: + +```elixir +defmodule KeyValueStore do + use GenServer + + # 客户端 API + def start_link(initial_state \\\\ %{}) do + GenServer.start_link(__MODULE__, initial_state, name: __MODULE__) + end + + def get(key) do + GenServer.call(__MODULE__, {:get, key}) + end + + def put(key, value) do + GenServer.cast(__MODULE__, {:put, key, value}) + end + + def delete(key) do + GenServer.call(__MODULE__, {:delete, key}) + end + + # 服务器回调 + @impl true + def init(initial_state) do + {:ok, initial_state} + end + + @impl true + def handle_call({:get, key}, _from, state) do + {:reply, Map.get(state, key), state} + end + + @impl true + def handle_call({:delete, key}, _from, state) do + {:reply, :ok, Map.delete(state, key)} + end + + @impl true + def handle_cast({:put, key, value}, state) do + {:noreply, Map.put(state, key, value)} + end +end + +# 使用示例 +{:ok, _pid} = KeyValueStore.start_link() +KeyValueStore.put(:name, "Elixir") +KeyValueStore.put(:version, "1.15") +IO.inspect(KeyValueStore.get(:name)) +``` + +### GenServer 回调详解 + +| 回调函数 | 用途 | 返回值 | +|----------|------|--------| +| `init/1` | 初始化状态 | `{:ok, state}` | +| `handle_call/3` | 同步请求 | `{:reply, reply, state}` | +| `handle_cast/2` | 异步请求 | `{:noreply, state}` | +| `handle_info/2` | 处理普通消息 | `{:noreply, state}` | +| `terminate/2` | 清理资源 | 任意 | + +## Supervisor 监督策略 + +### 容错哲学:Let it crash + +Elixir 的核心哲学是 **Let it crash**。进程崩溃不应该影响整个系统,Supervisor 会负责重启失败的进程。 + +```elixir +defmodule MyApp.Supervisor do + use Supervisor + + def start_link(init_arg) do + Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__) + end + + @impl true + def init(_init_arg) do + children = [ + # 工作进程 + {KeyValueStore, %{}}, + # 另一个工作进程 + {MyApp.Worker, []}, + # 动态监督者 + {DynamicSupervisor, strategy: :one_for_one, name: MyApp.DynamicSupervisor} + ] + + Supervisor.init(children, strategy: :one_for_all) + end +end +``` + +### 监督策略对比 + +| 策略 | 说明 | 适用场景 | +|------|------|----------| +| `:one_for_one` | 只重启失败的子进程 | 独立的服务 | +| `:one_for_all` | 重启所有子进程 | 相互依赖的服务 | +| `:rest_for_one` | 重启失败进程及其后续进程 | 有启动顺序依赖 | +| `:simple_one_for_one` | 动态添加子进程 | 需要动态创建进程 | + +### 重启策略 + +```elixir +defmodule MyApp.Worker do + use GenServer + + def start_link(args) do + GenServer.start_link(__MODULE__, args) + end + + @impl true + def init(args) do + # 如果初始化失败,Supervisor 会根据重启策略处理 + case connect_to_database(args) do + {:ok, conn} -> {:ok, conn} + {:error, reason} -> {:stop, reason} + end + end + + defp connect_to_database(_args) do + # 模拟数据库连接 + {:ok, %{} } + end +end +``` + +## 容错机制实践 + +### 链接进程(Linking) + +进程可以相互链接,一个进程崩溃会导致链接的进程也崩溃: + +```elixir +defmodule LinkExample do + def run do + parent = self() + + child = spawn_link(fn -> + receive do + :crash -> raise "Simulated error" + :normal -> IO.puts("Normal exit") + end + end) + + Process.flag(:trap_exit, true) + + send(child, :crash) + + receive do + {:EXIT, ^child, reason} -> + IO.puts("Child exited with reason: #{inspect(reason)}") + end + end +end +``` + +### 监控进程(Monitoring) + +监控是单向的,被监控进程崩溃不会导致监控进程崩溃: + +```elixir +defmodule MonitorExample do + def run do + target = spawn(fn -> + Process.sleep(1000) + exit(:normal) + end) + + ref = Process.monitor(target) + + receive do + {:DOWN, ^ref, :process, ^target, reason} -> + IO.puts("Process down: #{inspect(reason)}") + end + end +end +``` + +### 选择策略 + +| 特性 | Link | Monitor | +|------|------|---------| +| 方向 | 双向 | 单向 | +| 影响 | 相互影响 | 仅通知 | +| 用途 | 构建 Supervision Tree | 观察进程状态 | +| 退出传播 | 是 | 否 | + +## 分布式 Elixir + +### 节点间通信 + +Elixir 进程可以在不同机器间透明通信: + +```elixir +# 启动节点 1(机器 A) +# iex --sname node1@machine_a --cookie secret + +# 启动节点 2(机器 B) +# iex --sname node2@machine_b --cookie secret + +# 在 node2 上连接到 node1 +Node.connect(:"node1@machine_a") + +# 在 node2 上向 node1 发送消息 +send({:some_process, :"node1@machine_a"}, :hello_from_node2) + +# 在 node1 上接收 +receive do + msg -> IO.puts("Received: #{inspect(msg)}") +end +``` + +### 分布式任务 + +```elixir +defmodule DistributedTask do + def run_on_all_nodes(task) do + nodes = [Node.self() | Node.list()] + + Enum.map(nodes, fn node -> + Task.Supervisor.async_nolink({MyApp.TaskSupervisor, node}, fn -> + result = task.() + {node, result} + end) + end) + |> Enum.map(&Task.await/1) + end +end + +# 在所有节点上执行计算 +results = DistributedTask.run_on_all_nodes(fn -> + # 计算密集型任务 + Enum.sum(1..1_000_000) +end) + +IO.inspect(results) +``` + +## 实际应用:构建容错计数器 + +```elixir +defmodule FaultTolerantCounter do + use GenServer + + def start_link(_opts) do + GenServer.start_link(__MODULE__, 0, name: __MODULE__) + end + + def increment do + GenServer.cast(__MODULE__, :increment) + end + + def get_count do + GenServer.call(__MODULE__, :get_count) + end + + @impl true + def init(state) do + # 设置陷阱退出,处理链接进程的错误 + Process.flag(:trap_exit, true) + {:ok, state} + end + + @impl true + def handle_cast(:increment, state) do + {:noreply, state + 1} + end + + @impl true + def handle_call(:get_count, _from, state) do + {:reply, state, state} + end + + @impl true + def handle_info({:EXIT, _pid, reason}, state) do + IO.puts("Linked process exited: #{inspect(reason)}") + {:noreply, state} + end +end + +# Supervisor 配置 +defmodule CounterSupervisor do + use Supervisor + + def start_link(init_arg) do + Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__) + end + + @impl true + def init(_init_arg) do + children = [ + {FaultTolerantCounter, []} + ] + + # 使用 transient 重启策略,只在异常退出时重启 + Supervisor.init(children, + strategy: :one_for_one, + max_restarts: 5, + max_seconds: 10 + ) + end +end +``` + +## 总结 + +Elixir 的并发和容错能力源于其独特的设计哲学: + +1. **隔离性**:进程完全隔离,一个崩溃不影响其他 +2. **监督**:Supervisor 自动重启失败进程 +3. **消息传递**:避免共享状态带来的复杂性 +4. **热升级**:不停机更新运行中的系统 +5. **分布式透明**:跨节点通信与本地通信 API 一致 + +> "Let it crash" 不是忽视错误,而是设计系统时的基本假设 —— 组件会失败,但系统必须继续运行。 + +通过掌握这些概念,你可以构建出真正高可用、容错的分布式系统,充分利用多核 CPU 和集群环境。 +$doc$, + NULL, + 'published', + NOW() - INTERVAL '30 minutes', + NOW() - INTERVAL '30 minutes', + NOW() - INTERVAL '30 minutes' +), +( + 1, + 'Kotlin 协程与 Flow', + 'kotlin-coroutines-flow', + '全面解析 Kotlin 协程和 Flow,从基础概念到高级用法,深入理解结构化并发、冷流热流、状态管理,以及与 RxJava 的对比,掌握现代 Kotlin 异步编程。', + $doc$ +# Kotlin 协程与 Flow:现代异步编程指南 + +Kotlin 协程(Coroutines)彻底改变了 Android 和后端开发中的异步编程方式。本文将从基础概念出发,深入探讨协程的各种用法、Flow 响应式流,以及与 RxJava 的对比。 + +## 为什么选择协程? + +传统的回调式异步编程导致代码难以阅读和维护,俗称"回调地狱"。Kotlin 协程提供了**挂起函数(Suspending Functions)**,让异步代码像同步代码一样简洁。 + +> 协程不是线程,线程也不是协程。协程是**可挂起的计算**,可以在单个线程上运行多个协程。 + +## 协程基础 + +### 启动协程 + +Kotlin 提供了三种启动协程的方式: + +```kotlin +import kotlinx.coroutines.* + +fun main() = runBlocking { + // 1. launch - 启动新协程,不返回结果 + val job = launch { + delay(1000L) + println("World!") + } + + // 2. async - 启动新协程,返回 Deferred( future/promise) + val deferred = async { + delay(500L) + "Hello" + } + + // 3. runBlocking - 阻塞当前线程等待协程完成 + println("${deferred.await()}, ${job.join()}") +} +``` + +| 构建器 | 返回类型 | 用途 | +|--------|----------|------| +| `launch` | `Job` | 启动"开火即忘"的协程 | +| `async` | `Deferred` | 启动返回结果的协程 | +| `runBlocking` | `T` | 桥接阻塞和非阻塞代码 | + +### 挂起函数 + +挂起函数是协程的核心,可以在不阻塞线程的情况下暂停执行: + +```kotlin +import kotlinx.coroutines.* + +suspend fun fetchUserData(userId: String): User { + // 模拟网络请求 + delay(1000) // 挂起 1 秒,不阻塞线程 + return User(userId, "John Doe", "john@example.com") +} + +suspend fun fetchUserOrders(userId: String): List { + delay(800) + return listOf( + Order("1", 99.99), + Order("2", 149.99) + ) +} + +// 组合挂起函数 +suspend fun getUserProfile(userId: String): UserProfile { + val user = fetchUserData(userId) + val orders = fetchUserOrders(userId) + return UserProfile(user, orders) +} +``` + +### CoroutineScope 和上下文 + +```kotlin +import kotlinx.coroutines.* +import kotlin.coroutines.CoroutineContext + +// 自定义 Scope +class Activity : CoroutineScope { + private val job = Job() + + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + + fun destroy() { + job.cancel() // 取消所有子协程 + } + + fun loadData() { + launch { + try { + val data = fetchData() + updateUI(data) + } catch (e: CancellationException) { + println("Coroutine cancelled") + } + } + } + + private suspend fun fetchData(): String { + delay(2000) + return "Data loaded" + } + + private fun updateUI(data: String) { + println("UI updated: $data") + } +} +``` + +## 结构化并发 + +### 父子关系 + +协程形成树形结构,父协程取消会自动取消所有子协程: + +```kotlin +import kotlinx.coroutines.* + +fun main() = runBlocking { + val parentJob = launch { + // 子协程 1 + launch { + repeat(10) { i -> + println("Child 1: $i") + delay(100) + } + } + + // 子协程 2 + launch { + repeat(10) { i -> + println("Child 2: $i") + delay(150) + } + } + + delay(300) + println("Parent: Cancelling...") + } + + parentJob.join() + println("All coroutines completed") +} +``` + +### SupervisorJob + +当不希望子协程的失败影响其他子协程时,使用 SupervisorJob: + +```kotlin +import kotlinx.coroutines.* + +fun main() = runBlocking { + val supervisor = SupervisorJob() + + with(CoroutineScope(coroutineContext + supervisor)) { + // 第一个子协程 - 会失败 + val job1 = launch { + delay(100) + throw RuntimeException("Oops!") + } + + // 第二个子协程 - 不受影响继续运行 + val job2 = launch { + repeat(5) { i -> + println("Job2: $i") + delay(100) + } + } + + joinAll(job1, job2) + } +} +``` + +## Flow:响应式流 + +### 冷流(Cold Flow) + +Flow 是 Kotlin 的响应式流实现,采用**冷流**模式 —— 数据在订阅时才产生: + +```kotlin +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* + +fun simpleFlow(): Flow = flow { + println("Flow started") + for (i in 1..3) { + delay(100) + emit(i) // 发射值 + } +} + +fun main() = runBlocking { + val flow = simpleFlow() + + println("First collection:") + flow.collect { value -> + println("Received: $value") + } + + println("\nSecond collection:") + flow.collect { value -> + println("Received again: $value") + } +} +``` + +### 操作符 + +Flow 提供了丰富的操作符,类似于 RxJava: + +```kotlin +import kotlinx.coroutines.flow.* + +fun processNumbers(): Flow = flow { + (1..10).forEach { emit(it) } +} + +suspend fun demonstrateOperators() { + val result = processNumbers() + .filter { it % 2 == 0 } // 过滤偶数 + .map { it * it } // 平方 + .take(3) // 取前 3 个 + .onEach { println("Processing: $it") } + .toList() // 收集为列表 + + println("Result: $result") +} +``` + +| 操作符类别 | 示例 | 说明 | +|-----------|------|------| +| 转换 | `map`, `transform` | 转换每个元素 | +| 过滤 | `filter`, `take`, `drop` | 筛选元素 | +| 组合 | `zip`, `combine`, `merge` | 组合多个 Flow | +| 错误处理 | `catch`, `retry` | 处理异常 | +| 终端 | `collect`, `reduce`, `fold` | 收集结果 | + +### StateFlow 和 SharedFlow + +StateFlow 和 SharedFlow 是**热流**,适合状态管理和事件分发: + +```kotlin +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* + +class NewsViewModel { + // StateFlow - 总是有值,适合 UI 状态 + private val _uiState = MutableStateFlow(UiState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + // SharedFlow - 适合一次性事件 + private val _events = MutableSharedFlow() + val events: SharedFlow = _events.asSharedFlow() + + fun loadNews() { + viewModelScope.launch { + _uiState.value = UiState.Loading + + try { + val news = repository.fetchNews() + _uiState.value = UiState.Success(news) + } catch (e: Exception) { + _uiState.value = UiState.Error(e.message ?: "Unknown error") + _events.emit("Failed to load news") + } + } + } +} + +sealed class UiState { + object Loading : UiState() + data class Success(val data: List) : UiState() + data class Error(val message: String) : UiState() +} +``` + +## 异常处理 + +### try-catch 在协程中 + +```kotlin +import kotlinx.coroutines.* + +fun main() = runBlocking { + val job = launch { + try { + riskyOperation() + } catch (e: Exception) { + println("Caught: ${e.message}") + } + } + + job.join() +} + +suspend fun riskyOperation() { + delay(100) + throw RuntimeException("Something went wrong") +} +``` + +### CoroutineExceptionHandler + +```kotlin +import kotlinx.coroutines.* + +fun main() = runBlocking { + val handler = CoroutineExceptionHandler { _, exception -> + println("Caught $exception") + } + + val job = GlobalScope.launch(handler) { + throw AssertionError("My Error") + } + + val deferred = GlobalScope.async(handler) { + throw ArithmeticException() + } + + joinAll(job, deferred) +} +``` + +### Flow 异常处理 + +```kotlin +import kotlinx.coroutines.flow.* + +fun safeFlow(): Flow = flow { + emit(1) + emit(2) + throw RuntimeException("Error!") +}.catch { e -> + println("Caught: ${e.message}") + emit(-1) // 发射默认值 +}.onCompletion { cause -> + if (cause != null) { + println("Flow completed with error") + } else { + println("Flow completed successfully") + } +} +``` + +## 与 RxJava 对比 + +| 特性 | Kotlin Flow | RxJava | +|------|-------------|---------| +| 学习曲线 | 平缓 | 陡峭 | +| 内存开销 | 低 | 较高 | +| Android 支持 | 原生(官方推荐) | 需要额外依赖 | +| 线程切换 | `withContext` | `subscribeOn`/`observeOn` | +| 背压支持 | `buffer`, `conflate` | 内置 Backpressure | +| 取消机制 | 结构化并发 | Disposable | +| 冷/热流 | 明确区分 | 较模糊 | + +### 从 RxJava 迁移示例 + +```kotlin +// RxJava 方式 +Observable.fromIterable(users) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .map { it.name } + .subscribe { println(it) } + +// Flow 方式 +users.asFlow() + .flowOn(Dispatchers.IO) + .map { it.name } + .collect { println(it) } +``` + +## 实际应用:MVVM 架构 + +```kotlin +import androidx.lifecycle.* +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch + +class UserViewModel( + private val userRepository: UserRepository +) : ViewModel() { + + private val _searchQuery = MutableStateFlow("") + val searchQuery: StateFlow = _searchQuery.asStateFlow() + + // 搜索用户,自动去抖 + val users: StateFlow> = _searchQuery + .debounce(300) // 等待 300ms 无输入才搜索 + .flatMapLatest { query -> + if (query.isEmpty()) { + flowOf(emptyList()) + } else { + userRepository.searchUsers(query) + } + } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList() + ) + + fun onSearchQueryChange(query: String) { + _searchQuery.value = query + } + + fun refreshUsers() { + viewModelScope.launch { + try { + userRepository.refreshUsers() + } catch (e: Exception) { + // 处理错误 + } + } + } +} +``` + +## 总结 + +Kotlin 协程和 Flow 提供了一套完整的异步编程解决方案: + +1. **轻量级**:协程比线程更轻量,单线程可运行数千协程 +2. **结构化并发**:自动管理协程生命周期,避免泄漏 +3. **Flow 响应式**:声明式处理数据流,操作符丰富 +4. **异常安全**:完善的异常处理机制 +5. **与 Android 深度集成**:ViewModelScope、LifecycleScope 等 + +> 掌握协程和 Flow,是成为现代 Kotlin 开发者的必备技能。 + +通过本文的学习,你应该能够在实际项目中熟练使用协程进行网络请求、数据库操作、UI 更新等异步任务,并使用 Flow 构建响应式的数据流。 +$doc$, + NULL, + 'published', + NOW() - INTERVAL '15 minutes', + NOW() - INTERVAL '15 minutes', + NOW() - INTERVAL '15 minutes' +), +( + 1, + 'Swift 现代语法速览', + 'swift-modern-syntax', + '全面梳理 Swift 现代语法特性,从可选类型到属性包装器,深入理解值类型与引用类型、协议扩展、泛型和错误处理,掌握 Swift 编程的核心概念和最佳实践。', + $doc$ +# Swift 现代语法速览 + +Swift 是 Apple 推出的现代编程语言,结合了 C 和 Objective-C 的优点,同时引入了函数式编程和类型安全的特性。本文将全面梳理 Swift 的核心语法,帮助你快速掌握这门语言。 + +## 为什么选择 Swift? + +Swift 的设计目标是安全、快速、表达力强。它消除了 C 语言家族中许多不安全的特性,同时提供了现代化的语法和强大的类型系统。 + +> Swift 结合了编译型语言的性能和脚本语言的交互性,是现代 Apple 平台开发的首选语言。 + +## 基础语法 + +### 常量与变量 + +```swift +// 常量 - 不可变 +let pi = 3.14159 +let greeting = "Hello, Swift!" + +// 变量 - 可变 +var counter = 0 +counter += 1 + +// 类型注解(通常可以省略,编译器会自动推断) +let explicitDouble: Double = 70.0 +let implicitDouble = 70.0 // 同样是 Double + +// 多行字符串 +let multiline = """ + Swift is a powerful and intuitive + programming language for iOS, iPadOS, + macOS, watchOS, and tvOS. + """ +``` + +### 基本数据类型 + +| 类型 | 说明 | 示例 | +|------|------|------| +| `Int` | 整数 | `let age: Int = 25` | +| `Double` | 双精度浮点 | `let price: Double = 19.99` | +| `String` | 字符串 | `let name = "Swift"` | +| `Bool` | 布尔 | `let isActive = true` | +| `Array` | 数组 | `let numbers = [1, 2, 3]` | +| `Dictionary` | 字典 | `let scores = ["A": 90]` | + +### 字符串插值和运算 + +```swift +let name = "World" +let message = "Hello, \(name)!" + +// 字符串操作 +var str = "Swift" +str.append("!") +str += " Programming" + +// 多行字符串保留格式 +let poem = """ + Swift as the wind, + Strong as a mountain, + Elegant as poetry. + """ +``` + +## 可选类型(Optionals) + +### 什么是可选类型 + +可选类型表示一个值可能存在,也可能不存在(nil): + +```swift +// 可选整数 +var optionalNumber: Int? = 42 +optionalNumber = nil + +// 强制解包(危险!如果为 nil 会崩溃) +let forced = optionalNumber! + +// 安全解包方式 +if let number = optionalNumber { + print("The number is \(number)") +} else { + print("No number") +} + +// Nil 合并运算符 +let defaultNumber = optionalNumber ?? 0 + +// 可选链 +let uppercased = optionalNumber?.description.uppercased() +``` + +### guard 语句 + +`guard` 语句用于提前退出,提高代码可读性: + +```swift +func processUser(age: Int?, name: String?) { + guard let validAge = age, validAge >= 0 else { + print("Invalid age") + return + } + + guard let validName = name, !validName.isEmpty else { + print("Invalid name") + return + } + + print("User: \(validName), Age: \(validAge)") +} + +// 使用 +processUser(age: 25, name: "Alice") // 成功 +processUser(age: -5, name: "Bob") // Invalid age +processUser(age: 30, name: nil) // Invalid name +``` + +### if let 和 guard let 对比 + +| 特性 | `if let` | `guard let` | +|------|----------|-------------| +| 作用域 | 仅在 if 块内 | 在 guard 之后的代码 | +| 使用场景 | 条件分支处理 | 前置条件检查 | +| 代码风格 | 嵌套可能较深 | 减少嵌套,提前退出 | + +## 集合类型 + +### 数组和字典 + +```swift +// 数组 +var fruits = ["Apple", "Banana", "Orange"] +fruits.append("Mango") +fruits.insert("Grape", at: 1) +let firstFruit = fruits[0] + +// 字典 +var scores: [String: Int] = [ + "Alice": 95, + "Bob": 87, + "Charlie": 92 +] + +scores["David"] = 88 +if let aliceScore = scores["Alice"] { + print("Alice scored \(aliceScore)") +} + +// 遍历字典 +for (name, score) in scores { + print("\(name): \(score)") +} +``` + +### Set 集合 + +```swift +let favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] +let otherGenres: Set = ["Jazz", "Rock", "Electronic"] + +// 集合运算 +let intersection = favoriteGenres.intersection(otherGenres) +let union = favoriteGenres.union(otherGenres) +let difference = favoriteGenres.symmetricDifference(otherGenres) + +print("Both like: \(intersection)") +print("All genres: \(union)") +``` + +## 控制流 + +### 高级 switch + +```swift +let character: Character = "a" + +switch character { +case "a", "e", "i", "o", "u": + print("\(character) is a vowel") +case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", + "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": + print("\(character) is a consonant") +default: + print("\(character) is not a letter") +} + +// 区间匹配 +let count = 62 +switch count { +case 0: + print("none") +case 1..<5: + print("a few") +case 5..<12: + print("several") +case 12..<100: + print("dozens") +case 100..<1000: + print("hundreds") +default: + print("many") +} + +// 元组匹配 +let point = (1, 1) +switch point { +case (0, 0): + print("origin") +case (_, 0): + print("on x-axis") +case (0, _): + print("on y-axis") +case (-2...2, -2...2): + print("inside the box") +default: + print("outside") +} +``` + +### for-in 循环 + +```swift +// 遍历范围 +for index in 1...5 { + print("\(index) times 5 is \(index * 5)") +} + +// 遍历数组 +let names = ["Anna", "Alex", "Brian", "Jack"] +for name in names { + print("Hello, \(name)!") +} + +// 遍历字典 +let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] +for (animalName, legCount) in numberOfLegs { + print("\(animalName)s have \(legCount) legs") +} + +// 带索引的遍历 +for (index, name) in names.enumerated() { + print("\(index + 1). \(name)") +} + +// stride 函数 +let minutes = 60 +let minuteInterval = 5 +for tickMark in stride(from: 0, to: minutes, by: minuteInterval) { + print("Tick mark at \(tickMark) minutes") +} +``` + +## 函数和闭包 + +### 函数定义 + +```swift +// 基本函数 +func greet(person: String) -> String { + return "Hello, \(person)!" +} + +// 参数标签和参数名 +func greet(person: String, from hometown: String) -> String { + return "Hello \(person)! Glad you could visit from \(hometown)." +} + +let greeting = greet(person: "Bill", from: "Cupertino") + +// 默认参数 +func greet(person: String, nicely: Bool = true) -> String { + if nicely { + return "Hello, \(person)!" + } else { + return "Oh no, it's \(person) again..." + } +} + +print(greet(person: "Tim")) // Hello, Tim! +print(greet(person: "Tim", nicely: false)) // Oh no, it's Tim again... + +// 可变参数 +func arithmeticMean(_ numbers: Double...) -> Double { + var total: Double = 0 + for number in numbers { + total += number + } + return total / Double(numbers.count) +} + +print(arithmeticMean(1, 2, 3, 4, 5)) +``` + +### 闭包(Closures) + +```swift +// 基本闭包 +let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] + +// 完整写法 +let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in + return s1 > s2 +}) + +// 类型推断简化 +let reversedNames2 = names.sorted(by: { s1, s2 in return s1 > s2 }) + +// 隐式返回 +let reversedNames3 = names.sorted(by: { s1, s2 in s1 > s2 }) + +// 简写参数名 +let reversedNames4 = names.sorted(by: { \$0 > \$1 }) + +// 尾随闭包 +let reversedNames5 = names.sorted { \$0 > \$1 } + +// 捕获值 +func makeIncrementer(forIncrement amount: Int) -> () -> Int { + var runningTotal = 0 + func incrementer() -> Int { + runningTotal += amount + return runningTotal + } + return incrementer +} + +let incrementByTen = makeIncrementer(forIncrement: 10) +print(incrementByTen()) // 10 +print(incrementByTen()) // 20 +``` + +## 结构体与类 + +### 值类型 vs 引用类型 + +```swift +// 结构体 - 值类型 +struct Resolution { + var width = 0 + var height = 0 +} + +// 类 - 引用类型 +class VideoMode { + var resolution = Resolution() + var interlaced = false + var frameRate = 0.0 + var name: String? +} + +let hd = Resolution(width: 1920, height: 1080) +var cinema = hd // 值拷贝 +cinema.width = 2048 + +print("hd width: \(hd.width)") // 1920 +print("cinema width: \(cinema.width)") // 2048 + +let tenEighty = VideoMode() +tenEighty.resolution = hd +tenEighty.interlaced = true +tenEighty.name = "1080i" +tenEighty.frameRate = 25.0 + +let alsoTenEighty = tenEighty // 引用拷贝 +alsoTenEighty.frameRate = 30.0 + +print("tenEighty frameRate: \(tenEighty.frameRate)") // 30.0 +``` + +### 属性观察器 + +```swift +class StepCounter { + var totalSteps: Int = 0 { + willSet(newTotalSteps) { + print("About to set totalSteps to \(newTotalSteps)") + } + didSet { + if totalSteps > oldValue { + print("Added \(totalSteps - oldValue) steps") + } + } + } +} + +let stepCounter = StepCounter() +stepCounter.totalSteps = 200 +// About to set totalSteps to 200 +// Added 200 steps +stepCounter.totalSteps = 360 +// About to set totalSteps to 360 +// Added 160 steps +``` + +## 协议与扩展 + +### 协议定义 + +```swift +protocol FullyNamed { + var fullName: String { get } +} + +protocol RandomNumberGenerator { + func random() -> Double +} + +// 协议继承 +protocol NamedAndAged: FullyNamed { + var age: Int { get } +} + +// 遵循协议 +struct Person: FullyNamed { + var firstName: String + var lastName: String + + var fullName: String { + return "\(firstName) \(lastName)" + } +} + +let john = Person(firstName: "John", lastName: "Appleseed") +print(john.fullName) +``` + +### 扩展 + +```swift +// 扩展现有类型 +extension Int { + func repetitions(task: () -> Void) { + for _ in 0.. Bool { + return random() > 0.5 + } +} +``` + +## 泛型 + +### 泛型函数和类型 + +```swift +// 泛型函数 +func swapTwoValues(_ a: inout T, _ b: inout T) { + let temporaryA = a + a = b + b = temporaryA +} + +var someInt = 3 +var anotherInt = 107 +swapTwoValues(&someInt, &anotherInt) + +var someString = "hello" +var anotherString = "world" +swapTwoValues(&someString, &anotherString) + +// 泛型类型 +struct Stack { + private var items: [Element] = [] + + mutating func push(_ item: Element) { + items.append(item) + } + + mutating func pop() -> Element { + return items.removeLast() + } + + var topItem: Element? { + return items.isEmpty ? nil : items[items.count - 1] + } + + var isEmpty: Bool { + return items.isEmpty + } +} + +var stackOfStrings = Stack() +stackOfStrings.push("uno") +stackOfStrings.push("dos") +stackOfStrings.push("tres") +print(stackOfStrings.pop()) // tres + +// 泛型约束 +func findIndex(of valueToFind: T, in array: [T]) -> Int? { + for (index, value) in array.enumerated() { + if value == valueToFind { + return index + } + } + return nil +} +``` + +## 错误处理 + +### 定义和抛出错误 + +```swift +enum PrinterError: Error { + case outOfPaper + case noToner + case onFire +} + +func send(job: Int, toPrinter printerName: String) throws -> String { + if printerName == "Never Has Toner" { + throw PrinterError.noToner + } + return "Job sent" +} + +// 使用 do-catch +func processPrintJob() { + do { + let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng") + print(printerResponse) + } catch PrinterError.onFire { + print("I'll just put this over here, with the rest of the fire.") + } catch let printerError as PrinterError { + print("Printer error: \(printerError).") + } catch { + print(error) + } +} + +// try? 转换为可选 +let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler") +let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner") + +// defer +func processFile(filename: String) throws { + let file = open(filename) + defer { + close(file) + } + // 处理文件... + // 无论是否抛出错误,defer 都会执行 +} +``` + +## 属性包装器 + +```swift +@propertyWrapper +struct TwelveOrLess { + private var number = 0 + var wrappedValue: Int { + get { return number } + set { number = min(newValue, 12) } + } +} + +struct SmallRectangle { + @TwelveOrLess var height: Int + @TwelveOrLess var width: Int +} + +var rectangle = SmallRectangle() +print(rectangle.height) // 0 + +rectangle.height = 10 +print(rectangle.height) // 10 + +rectangle.height = 24 +print(rectangle.height) // 12 +``` + +## 总结 + +Swift 是一门现代化的编程语言,提供了丰富的特性: + +1. **类型安全**:可选类型消除空指针异常 +2. **值类型优先**:结构体和枚举都是值类型,减少副作用 +3. **协议导向**:通过协议和扩展实现多态 +4. **函数式特性**:闭包、高阶函数、不可变性 +5. **现代语法**:类型推断、字符串插值、模式匹配 + +> Swift 的设计哲学是安全、快速、表达力强。它不仅仅是一门 iOS 开发语言,也是一门通用的现代编程语言。 + +通过本文的学习,你应该已经掌握了 Swift 的核心语法特性,可以开始构建 iOS、macOS 或其他平台的应用程序了。 +$doc$, + NULL, + 'published', + NOW(), + NOW(), + NOW() +) +ON CONFLICT (slug) DO NOTHING; + +-- 重置序列 +SELECT setval('posts_id_seq', COALESCE((SELECT MAX(id) FROM posts), 1), false); diff --git a/src/components/frontend_layout.rs b/src/components/frontend_layout.rs index 25592cd..c48eed6 100644 --- a/src/components/frontend_layout.rs +++ b/src/components/frontend_layout.rs @@ -3,11 +3,25 @@ use dioxus::prelude::*; use crate::components::footer::Footer; use crate::components::header::Header; use crate::components::nav::use_nav_items; -use crate::components::skeletons::home_skeleton::HomeSkeleton; +use crate::components::skeletons::archive_skeleton::ArchiveSkeleton; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; +use crate::components::skeletons::home_skeleton::HomeSkeleton; +use crate::components::skeletons::post_detail_skeleton::PostDetailSkeleton; +use crate::components::skeletons::search_skeleton::SearchSkeleton; +use crate::components::skeletons::tags_skeleton::TagsSkeleton; use crate::router::Route; use crate::theme::ThemeToggle; +fn route_skeleton(route: &Route) -> Element { + match route { + Route::Archives {} => rsx! { DelayedSkeleton { ArchiveSkeleton {} } }, + Route::Tags {} | Route::TagDetail { .. } => rsx! { DelayedSkeleton { TagsSkeleton {} } }, + Route::Search {} => rsx! { DelayedSkeleton { SearchSkeleton {} } }, + Route::PostDetail { .. } => rsx! { DelayedSkeleton { PostDetailSkeleton {} } }, + _ => rsx! { DelayedSkeleton { HomeSkeleton {} } }, + } +} + #[component] pub fn FrontendLayout() -> Element { let route = use_route::(); @@ -18,9 +32,7 @@ pub fn FrontendLayout() -> Element { Header { nav_items, right_content: rsx! { ThemeToggle {} } } main { class: "flex-1 w-full max-w-3xl mx-auto px-6 py-6", SuspenseBoundary { - fallback: |_| rsx! { - DelayedSkeleton { HomeSkeleton {} } - }, + fallback: move |_| route_skeleton(&route), Outlet:: {} } }