AI Library · 知识库

# 能力约束:排序之前先要求可比较

学习目标:区分“能存储 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++ 工作草案:排序要求为持续更新资料。

# 迁移误区与适用限制

# 运行与验证

每种语言在独立目录按上节文件名和命令执行。Rust/C++ 的可执行程序在 PowerShell 中用 .\main.exe,POSIX shell 中用 ./main。基线见主题首页,实际执行证据集中记入验证记录。本文给出预期语义与检查,不把编写或阅读核对宣称为运行通过;各程序应由验证流程执行,类型错误反例的文字说明不等于已运行负向编译测试。

# 相关阅读