diff --git a/Cargo.lock b/Cargo.lock index 03818c8..ae8900f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5816,6 +5816,7 @@ dependencies = [ "dioxus", "dotenvy", "getrandom 0.2.17", + "governor", "http", "image", "js-sys", diff --git a/scripts/seed_posts.sql b/scripts/seed_posts.sql deleted file mode 100644 index deab74a..0000000 --- a/scripts/seed_posts.sql +++ /dev/null @@ -1,6284 +0,0 @@ --- 种子数据:高质量测试文章 --- 共 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);