# 能力约束:排序之前先要求可比较
学习目标:区分“能存储 T”与“能对 T 排序”,并识别类型约束无法证明的排序规律。
前置知识:泛型函数、列表;比较器只需理解负数/零/正数代表小于/等于/大于。
# 问题与边界
将一个可变整数序列原地升序排列;示例同时检查重复值、负数、空序列。输入限于本例可精确表示的整数,不接收 NaN、混合类型或外部未校验对象。语言允许扩展到其他类型,但扩展时必须满足一致比较规则;本例不统一 Unicode 字符串的各语言排序语义。返回后原序列变成 [-1, 2, 2]。空输入正常完成,不是失败;不支持的静态类型应在工具阶段拒绝,动态输入的调用方要报告运行时失败。
# 关键差异
| 机制 | 能检查的事实 | 仍由实现者保证 |
|---|---|---|
| Rust Ord / Java、Kotlin Comparable | 提供约定的比较操作 | 全序、传递性与相等一致性 |
| Go 类型集 | 类型支持 < 等运算 |
NaN 等值域边界 |
| TS/Python 比较器签名 | 参数及返回类型 | 比较结果的规律 |
| C++17 模板 | 实例化所需表达式有效 | strict weak ordering |
| JS 回调 | 执行时可调用 | 数据形状和全部比较规律 |
本题没有一个通吃的“Comparable”:内建运算符、名义接口、结构类型和模板要求不是同一机制。
# 各语言实现
# Rust
成功时无输出,所有语义检查通过。
Rustfn order<T: Ord>(items: &mut [T]) { items.sort(); }
fn main() {
let mut items = [2, -1, 2];
order(&mut items);
assert_eq!(items, [-1, 2, 2]);
let mut empty: [i32; 0] = [];
order(&mut empty);
assert!(empty.is_empty());
}
Ord 比 PartialOrd 更强。f64 没有 Ord,不能直接传入这个函数;若需要浮点排序,应显式决定 NaN 语义后使用 total_cmp,不是删除约束。Ord 契约。
# Go
成功时无输出,所有语义检查通过。
Gopackage main
import ("cmp"; "slices")
func order[T cmp.Ordered](items []T) { slices.Sort(items) }
func main() {
items := []int{2, -1, 2}
order(items)
if !slices.Equal(items, []int{-1, 2, 2}) { panic("order") }
empty := []int{}
order(empty)
if len(empty) != 0 { panic("empty") }
}
cmp.Ordered 是类型集,允许整数、浮点和字符串;本文业务值域比它窄。不要从 Go 接受 float64 推断它与 Rust Ord 等价;slices.Sort 对 NaN 有自己的规则。cmp.Ordered、slices.Sort。
# Java
成功时预期输出为 "not mutable\n"(\n 表示换行)。
Javaimport java.util.ArrayList;
import java.util.List;
public class Main {
static <T extends Comparable<? super T>> void order(List<T> items) { items.sort(null); }
public static void main(String[] args) {
var items = new ArrayList<>(List.of(2, -1, 2));
order(items);
if (!items.equals(List.of(-1, 2, 2))) throw new AssertionError();
var empty = new ArrayList<Integer>();
order(empty);
if (!empty.isEmpty()) throw new AssertionError();
try { order(List.of(2, 1)); throw new AssertionError("expected failure"); }
catch (UnsupportedOperationException e) { System.out.println("not mutable"); }
}
}
? super T 允许 T 继承父类型的比较能力;可比较并不等于列表可修改,所以驱动展示不可变列表的真实失败调用方。Comparable、List.sort。
# JavaScript
成功时无输出,所有语义检查通过。
JavaScriptfunction order(items, compare) { items.sort(compare); }
const compare = (a, b) => a - b;
const items = [2, -1, 2];
order(items, compare);
if (JSON.stringify(items) !== "[-1,2,2]") throw new Error("order");
const empty = [];
order(empty, compare);
if (empty.length !== 0) throw new Error("empty");
JS 默认 sort 按字符串排序,不能直接用于整数升序。这里把比较规则作为显式能力传入,而不是假设所有值都有 compareTo。ECMAScript Array.sort。
# TypeScript
TypeScriptfunction order<T>(items: T[], compare: (a: T, b: T) => number): void { items.sort(compare); }
const compare = (a: number, b: number): number => a - b;
const items = [2, -1, 2];
order(items, compare);
if (JSON.stringify(items) !== "[-1,2,2]") throw new Error("order");
const empty: number[] = [];
order(empty, compare);
if (empty.length !== 0) throw new Error("empty");
这里约束的是回调签名,不是 T 的继承关系。返回 number 不证明比较器传递,也不能保证不返回 NaN。TypeScript 函数类型。
# Kotlin
成功时无输出,所有语义检查通过。
Kotlinfun <T : Comparable<T>> order(items: MutableList<T>) { items.sort() }
fun main() {
val items = mutableListOf(2, -1, 2)
order(items)
check(items == listOf(-1, 2, 2))
val empty = mutableListOf<Int>()
order(empty)
check(empty.isEmpty())
}
MutableList 在签名中表达修改意图,但仍要遵守实现的可修改约定。Kotlin 可用比较器扩展不具有自然顺序的记录,而不必修改记录类型。Kotlin 排序。
# Python
成功时预期输出为 "not comparable\n"(\n 表示换行)。
Pythonfrom functools import cmp_to_key
from typing import Callable, TypeVar
T = TypeVar("T")
def order(items: list[T], compare: Callable[[T, T], int]) -> None:
items.sort(key=cmp_to_key(compare))
def compare(a: int, b: int) -> int:
return (a > b) - (a < b)
items = [2, -1, 2]
order(items, compare)
assert items == [-1, 2, 2]
empty: list[int] = []
order(empty, compare)
assert empty == []
try:
compare(1, "bad")
except TypeError:
print("not comparable")
else:
raise AssertionError("expected failure")
一般业务排序应优先 key=,这里只用 cmp_to_key 对照比较器。末尾故意违反已注解的调用契约,展示解释器不会因注解提前拒绝;静态工具也应报告该行,因此本篇不是“静态检查零诊断”示例。cmp_to_key。
# C++
成功时无输出,所有语义检查通过。
C++#include <algorithm>
#include <cassert>
#include <vector>
template<class T> void order(std::vector<T>& items) {
std::sort(items.begin(), items.end());
}
int main() {
std::vector<int> items{2, -1, 2};
order(items);
assert((items == std::vector<int>{-1, 2, 2}));
std::vector<int> empty;
order(empty);
assert(empty.empty());
}
C++17 此处没有显式 requires;缺失 < 的对象会在模板实例化相关诊断中失败。比较器必须满足严格弱序;违反要求不是一个可靠可捕获的业务失败通道。C++ 工作草案:排序要求为持续更新资料。
# 迁移误区与适用限制
- “能编译的比较器就是正确顺序” →
a<b与b<a同时为真会破坏算法约定;类型只能检查形状,不能替代排序性质测试。 - “可比较就可原地排序” → Java 的不可变列表仍失败;类型能力和容器修改能力分开审查。
- “所有排序都是稳定的” → Rust 的 sort、Java List.sort 等与 C++ std::sort 的稳定性不同;需要保留同分记录顺序时显式选择稳定算法,本例相等整数不可观察这一区别。
- 负向调用不是可恢复默认值策略。Python 驱动报告不兼容,Java 驱动报告不可修改;库调用者若继续执行,应先取得满足契约的数据,而不是使用部分排序结果。
# 运行与验证
每种语言在独立目录按上节文件名和命令执行。Rust/C++ 的可执行程序在 PowerShell 中用 .\main.exe,POSIX shell 中用 ./main。基线见主题首页,实际执行证据集中记入验证记录。本文给出预期语义与检查,不把编写或阅读核对宣称为运行通过;各程序应由验证流程执行,类型错误反例的文字说明不等于已运行负向编译测试。