AI Library · 知识库

# 函数作为参数:把筛选规则交给调用方

学习目标:让遍历流程保持不变,分别传入“正数”和“偶数”规则,并看清回调的执行与失败责任。

前置知识:函数、循环、布尔表达式;lambda 是内联定义的小函数,本篇不要求理解捕获。

# 问题与边界

对有限整数序列计数:从左至右,对每个元素同步调用规则一次,返回满足规则的个数,不修改输入,不保存规则。输入 [-2, 0, 3, 4],正数规则得到 2,偶数规则得到 3;空输入返回 0 且不调用规则。不分配筛选结果容器,因为任务只需要数量。规则不能在遍历期间修改输入;如果规则失败,整个调用失败,没有有效计数结果,也不自动重试。

# 关键差异

选择 在本例的作用 成本或限制
Rust 泛型 FnMut / C++ 模板 调用可内联的具体可调用类型 不同类型可产生不同实例
Java IntPredicate 统一函数式接口,直接接收 int 不是任意同名方法对象
Go 函数值 / Kotlin 函数类型 参数明确表示可调用签名 具体运行表示由实现决定
JS 动态回调 / TS 签名 运行逻辑相同 TS 注解不验证回调真值语义
Python Callable 可读的静态契约 注解不拦截运行时异常

这里“高阶”仅指接收函数,不意味着并发、惰性求值或函数式程序必然没有状态。

# 各语言实现

# Rust

成功时无输出,所有语义检查通过。

Rustfn count_if(items: &[i32], mut rule: impl FnMut(i32) -> bool) -> usize {
    let mut count = 0;
    for &item in items { if rule(item) { count += 1; } }
    count
}
fn positive(n: i32) -> bool { n > 0 }
fn main() {
    let items = [-2, 0, 3, 4];
    assert_eq!(count_if(&items, positive), 2);
    assert_eq!(count_if(&items, |n| n % 2 == 0), 3);
    assert_eq!(count_if(&[], |_| panic!("must not run")), 0);
}

FnMut 接受可变捕获状态的回调,也能接收普通函数。Fn/FnMut/FnOnce 的差别不是返回值类型;本例循环多次调用,不应仅要求 FnOnce。Rust 闭包 trait。

# Go

成功时无输出,所有语义检查通过。

Gopackage main
func countIf(items []int, rule func(int) bool) int {
    count := 0
    for _, item := range items { if rule(item) { count++ } }
    return count
}
func positive(n int) bool { return n > 0 }
func main() {
    items := []int{-2, 0, 3, 4}
    if countIf(items, positive) != 2 || countIf(items, func(n int) bool { return n%2 == 0 }) != 3 { panic("count") }
    if countIf(nil, func(int) bool { panic("must not run") }) != 0 { panic("empty") }
}

传函数值时没有调用括号;positive() 会立即调用且缺少实参。nil 函数值不是可调用规则,调用方必须传入真实函数。Go 函数类型。

# Java

成功时预期输出为 "rule failed\n"(\n 表示换行)。

Javaimport java.util.function.IntPredicate;
public class Main {
    static int countIf(int[] items, IntPredicate rule) {
        int count = 0;
        for (int item : items) if (rule.test(item)) count++;
        return count;
    }
    static boolean positive(int n) { return n > 0; }
    public static void main(String[] args) {
        int[] items = {-2, 0, 3, 4};
        if (countIf(items, Main::positive) != 2 || countIf(items, n -> n%2 == 0) != 3) throw new AssertionError();
        if (countIf(new int[0], n -> { throw new AssertionError("must not run"); }) != 0) throw new AssertionError();
        try { countIf(items, n -> { throw new IllegalStateException("rule failed"); }); throw new AssertionError(); }
        catch (IllegalStateException e) { System.out.println(e.getMessage()); }
    }
}

选择 IntPredicate 避免为每个 int 装箱成 Integer。方法引用与 lambda 都适配这个函数式接口;受检异常不能未经处理直接穿过此接口签名。调用方在失败后不使用不存在的计数。IntPredicate。

# JavaScript

成功时预期输出为 "rule failed\n"(\n 表示换行)。

JavaScriptfunction countIf(items, rule) {
    let count = 0;
    for (const item of items) if (rule(item)) count++;
    return count;
}
function positive(n) { return n > 0; }
const items = [-2, 0, 3, 4];
if (countIf(items, positive) !== 2 || countIf(items, n => n % 2 === 0) !== 3) throw new Error("count");
if (countIf([], () => { throw new Error("must not run"); }) !== 0) throw new Error("empty");
try {
    countIf(items, () => { throw new Error("rule failed"); });
    throw new Error("unexpected success");
} catch (e) {
    if (!(e instanceof Error) || e.message !== "rule failed") throw e;
    console.log(e.message);
}

同步回调不是 Promise 协议:传入 async 函数会返回真值对象,使这个计数函数错误地把元素计入;异步筛选应另定义等待策略。这里的规则必须返回 boolean。ECMAScript 函数调用。

# TypeScript

TypeScriptfunction countIf(items: readonly number[], rule: (n: number) => boolean): number {
    let count = 0;
    for (const item of items) if (rule(item)) count++;
    return count;
}
function positive(n: number): boolean { return n > 0; }
const items = [-2, 0, 3, 4];
if (countIf(items, positive) !== 2 || countIf(items, n => n % 2 === 0) !== 3) throw new Error("count");
if (countIf([], () => { throw new Error("must not run"); }) !== 0) throw new Error("empty");
try {
    countIf(items, () => { throw new Error("rule failed"); });
    throw new Error("unexpected success");
} catch (e) {
    if (!(e instanceof Error) || e.message !== "rule failed") throw e;
    console.log(e.message);
}

显式 boolean 返回类型使 async 回调不符合签名;这保护的是类型化调用,不是任意外部 JavaScript 调用。TypeScript 函数表达式类型。

# Kotlin

成功时无输出,所有语义检查通过。

Kotlinfun countIf(items: IntArray, rule: (Int) -> Boolean): Int {
    var count = 0
    for (item in items) if (rule(item)) count++
    return count
}
fun positive(n: Int): Boolean = n > 0
fun main() {
    val items = intArrayOf(-2, 0, 3, 4)
    check(countIf(items, ::positive) == 2)
    check(countIf(items) { it % 2 == 0 } == 3)
    check(countIf(intArrayOf()) { error("must not run") } == 0)
}

尾随 lambda 只是把最后一个函数参数移到括号外,不改变同步执行时机。本例不加 inline;若性能分析证明回调开销重要,再评估内联及非局部返回等语义影响。Kotlin 高阶函数。

# Python

成功时预期输出为 "rule failed\n"(\n 表示换行)。

Pythonfrom collections.abc import Callable, Sequence
def count_if(items: Sequence[int], rule: Callable[[int], bool]) -> int:
    count = 0
    for item in items:
        if rule(item):
            count += 1
    return count
def positive(n: int) -> bool:
    return n > 0
def failed(n: int) -> bool:
    raise ValueError("rule failed")
items = [-2, 0, 3, 4]
assert count_if(items, positive) == 2
assert count_if(items, lambda n: n % 2 == 0) == 3
assert count_if([], failed) == 0
try:
    count_if(items, failed)
except ValueError as e:
    print(e)
else:
    raise AssertionError("unexpected success")

函数对象可直接传递;lambda 并非唯一形式,复杂规则应使用命名函数。异常在调用回调的那次迭代传播,调用方负责报告而非返回部分计数。Python 函数定义。

# C++

成功时无输出,所有语义检查通过。

C++#include <cassert>
#include <vector>
template<class Rule>
std::size_t count_if(const std::vector<int>& items, Rule rule) {
    std::size_t count = 0;
    for (int item : items) if (rule(item)) ++count;
    return count;
}
bool positive(int n) { return n > 0; }
int main() {
    const std::vector<int> items{-2, 0, 3, 4};
    assert(count_if(items, positive) == 2);
    assert(count_if(items, [](int n) { return n % 2 == 0; }) == 3);
    assert(count_if({}, [](int) -> bool { throw 1; }) == 0);
}

按值接收本例的函数指针或无捕获 lambda 不需要动态分配。并非所有场合都需要 std::function;当必须存储异构可调用值时才考虑类型擦除及其成本。C++ 工作草案:lambda持续更新,示例使用 C++17。

# 迁移误区与适用限制

# 运行与验证

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

# 相关阅读