强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

04 一等公民函数

04 一等公民函数

“函数是一等公民——它可以被传递、被返回、被赋值给变量,就像数字和字符串一样。”


4.1 函数是一等公民

当一门语言将函数视为一等公民(First-Class Citizen),意味着函数与其他值(数字、字符串、对象)享有同等地位。

4.1.1 一等公民的能力

能力说明示例
赋值给变量函数可绑定到变量const f = (x) => x + 1
作为参数传递函数可传给其他函数[1,2].map(x => x * 2)
作为返回值函数可从函数返回const add = (x) => (y) => x + y
存储在数据结构中函数可放入数组/对象{add: (a,b) => a+b}
匿名创建无需命名即可创建lambda x: x + 1

4.1.2 各语言的函数表示

Haskell:

-- Haskell 中函数天然是一等公民
f :: Int -> Int
f = \x -> x + 1

-- 函数列表
funcs :: [Int -> Int]
funcs = [(+1), (*2), (^2)]

-- 应用函数列表
result :: [Int]
result = map ($ 5) funcs  -- [6, 10, 25]

JavaScript:

// 赋值给变量
const double = (x) => x * 2;

// 作为参数
const apply = (fn, x) => fn(x);
apply(double, 5);  // 10

// 作为返回值
const multiply = (a) => (b) => a * b;
const triple = multiply(3);
triple(10);  // 30

// 存储在数据结构中
const operations = {
  add: (a, b) => a + b,
  sub: (a, b) => a - b,
  mul: (a, b) => a * b,
};
operations.add(2, 3);  // 5

Python:

# 赋值给变量
double = lambda x: x * 2

# 作为参数
def apply(fn, x):
    return fn(x)

apply(double, 5)  # 10

# 作为返回值
def multiply(a):
    return lambda b: a * b

triple = multiply(3)
triple(10)  # 30

# 存储在数据结构中
operations = {
    'add': lambda a, b: a + b,
    'sub': lambda a, b: a - b,
    'mul': lambda a, b: a * b,
}
operations['add'](2, 3)  # 5

Rust:

// 赋值给变量
let double = |x: i32| x * 2;

// 作为参数
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}

apply(double, 5);  // 10

// 作为返回值(闭包)
fn multiply(a: i32) -> impl Fn(i32) -> i32 {
    move |b| a * b
}

let triple = multiply(3);
triple(10);  // 30

// 存储在 HashMap 中
use std::collections::HashMap;
let mut ops: HashMap<&str, Box<dyn Fn(i32, i32) -> i32>> = HashMap::new();
ops.insert("add", Box::new(|a, b| a + b));

Clojure:

;; 赋值给变量
(def double (fn [x] (* x 2)))

;; 作为参数
(defn apply-fn [f x] (f x))
(apply-fn double 5)  ;; 10

;; 作为返回值
(defn multiply [a]
  (fn [b] (* a b)))

(def triple (multiply 3))
(triple 10)  ;; 30

;; 存储在 map 中
(def operations
  {:add (fn [a b] (+ a b))
   :sub (fn [a b] (- a b))
   :mul (fn [a b] (* a b))})
((:add operations) 2 3)  ;; 5

4.2 高阶函数(Higher-Order Function)

高阶函数是接收函数作为参数或返回函数作为结果的函数。

4.2.1 常见高阶函数

高阶函数类型签名 (Haskell)用途
map(a -> b) -> [a] -> [b]转换每个元素
filter(a -> Bool) -> [a] -> [a]筛选元素
reduce/fold(b -> a -> b) -> b -> [a] -> b归约为单个值
sort(a -> a -> Ordering) -> [a] -> [a]自定义排序
compose(b -> c) -> (a -> b) -> a -> c函数组合
curry((a, b) -> c) -> a -> b -> c柯里化
partial-偏应用
memoize-缓存函数结果

4.2.2 自定义高阶函数

JavaScript:

// retry:重试函数
const retry = (fn, maxAttempts = 3) => async (...args) => {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn(...args);
    } catch (err) {
      if (attempt === maxAttempts) throw err;
      await new Promise(r => setTimeout(r, 2 ** attempt * 100));
    }
  }
};

// once:只执行一次
const once = (fn) => {
  let called = false;
  let result;
  return (...args) => {
    if (!called) {
      called = true;
      result = fn(...args);
    }
    return result;
  };
};

// tap:调试用,不改变值
const tap = (fn) => (x) => {
  fn(x);
  return x;
};

// 使用示例
const result = [1, 2, 3, 4, 5]
  .map(x => x * 2)
  .filter(x => x > 4)
  .reduce((sum, x) => sum + x, 0);
// result = 24

Haskell:

-- retry
retry :: Int -> IO a -> IO a
retry 0 action = action
retry n action = action `catch` \(_ :: SomeException) -> retry (n-1) action

-- once(使用 IORef)
once :: IO a -> IO (IO a)
once action = do
  ref <- newIORef Nothing
  return $ do
    cached <- readIORef ref
    case cached of
      Just val -> return val
      Nothing  -> do
        val <- action
        writeIORef ref (Just val)
        return val

-- tap
tap :: (a -> IO ()) -> a -> IO a
tap f x = f x >> return x

4.3 函数组合(Function Composition)

函数组合是将多个函数串联起来形成新函数的技术。

4.3.1 数学定义

(f ∘ g)(x) = f(g(x))

即:先执行 g,再执行 f

4.3.2 各语言的组合方式

Haskell:

-- 使用 . 运算符
-- f . g = \x -> f (g x)
-- 从右到左组合

addOne :: Int -> Int
addOne = (+1)

double :: Int -> Int
double = (*2)

-- doubleAfterAddOne = double . addOne
-- doubleAfterAddOne x = double (addOne x)
doubleAfterAddOne :: Int -> Int
doubleAfterAddOne = double . addOne

-- doubleAfterAddOne 3 == 8  (3+1=4, 4*2=8)

-- 多函数组合
pipeline :: String -> String
pipeline = unwords . map (take 10) . words . map toLower

JavaScript:

// 手动组合
const compose = (...fns) =>
  (x) => fns.reduceRight((acc, fn) => fn(acc), x);

// 管道(从左到右)
const pipe = (...fns) =>
  (x) => fns.reduce((acc, fn) => fn(acc), x);

// 使用
const addOne = (x) => x + 1;
const double = (x) => x * 2;
const toString = (x) => x.toString();

const doubleAfterAddOne = compose(double, addOne);
doubleAfterAddOne(3);  // 8

const pipeline = pipe(addOne, double, toString);
pipeline(3);  // "8"

// Ramda 库
const R = require('ramda');
const transform = R.pipe(
  R.map(R.prop('name')),
  R.filter(name => name.length > 3),
  R.sortBy(R.identity),
  R.join(', ')
);

Python:

from functools import reduce

# 手动组合
def compose(*fns):
    return lambda x: reduce(lambda acc, fn: fn(acc), reversed(fns), x)

# 管道
def pipe(*fns):
    return lambda x: reduce(lambda acc, fn: fn(acc), fns, x)

add_one = lambda x: x + 1
double = lambda x: x * 2
to_str = lambda x: str(x)

double_after_add_one = compose(double, add_one)
double_after_add_one(3)  # 8

pipeline = pipe(add_one, double, to_str)
pipeline(3)  # "8"

# toolz 库
from toolz import pipe, compose
from toolz.curried import map, filter, pipe

Rust:

// Rust 没有内置组合,但可以用函数式方法链
let result: i32 = (1..=5)
    .map(|x| x + 1)
    .filter(|x| x > &3)
    .sum();

// 或使用自定义组合宏
macro_rules! compose {
    ($f:expr) => { $f };
    ($f:expr, $($rest:expr),+) => {
        move |x| compose!($($rest),+)( $f(x) )
    };
}

fn add_one(x: i32) -> i32 { x + 1 }
fn double(x: i32) -> i32 { x * 2 }

let f = compose!(add_one, double);
f(3)  // (3*2)+1 = 7

Clojure:

;; 使用 comp(从右到左)
(def double-after-add-one (comp #(* 2 %) #(+ 1 %)))
(double-after-add-one 3)  ;; 8

;; 使用 -> 和 ->> 宏(从左到右)
(-> 3
    (+ 1)      ;; 4
    (* 2)      ;; 8
    str)       ;; "8"

;; 管道操作
(->> [1 2 3 4 5]
     (map #(* 2 %))      ;; (2 4 6 8 10)
     (filter #(> % 4))   ;; (6 8 10)
     (reduce +))          ;; 24

4.3.3 组合定律

函数组合满足以下定律:

定律表达式含义
结合律(f ∘ g) ∘ h ≡ f ∘ (g ∘ h)组合顺序不影响结果
单位元id ∘ f ≡ f ∘ id ≡ f恒等函数是组合的单位元
-- 结合律验证
-- (f . g) . h = f . (g . h)

-- 单位元
id :: a -> a
id x = x

-- id . f = f
-- f . id = f

4.4 柯里化(Currying)

柯里化是将多参数函数转换为一系列单参数函数的技术。

4.4.1 定义

未柯里化: f(a, b, c) → result
柯里化后: f(a)(b)(c) → result

等价类型转换: (a, b) → c  ≡  a → (b → c)

4.4.2 各语言实现

Haskell:

-- Haskell 中函数天然柯里化
add :: Int -> Int -> Int
add x y = x + y

-- 等价于
-- add :: Int -> (Int -> Int)
-- add = \x -> \y -> x + y

add3 :: Int -> Int
add3 = add 3      -- 部分应用

add3 5            -- 8
add3 10           -- 13

-- map 也是柯里化的
map :: (a -> b) -> [a] -> [b]

-- 部分应用 map
doubleAll :: [Int] -> [Int]
doubleAll = map (*2)

JavaScript:

// 手动柯里化
const curry = (fn) => {
  const arity = fn.length;
  const curried = (...args) =>
    args.length >= arity
      ? fn(...args)
      : (...moreArgs) => curried(...args, ...moreArgs);
  return curried;
};

// 使用
const add = curry((a, b) => a + b);
const add3 = add(3);
add3(5);   // 8
add3(10);  // 13

// 多参数柯里化
const sum3 = curry((a, b, c) => a + b + c);
sum3(1)(2)(3);    // 6
sum3(1, 2)(3);    // 6
sum3(1)(2, 3);    // 6
sum3(1, 2, 3);    // 6

// Ramda 自动柯里化
const R = require('ramda');
const multiply = R.multiply(3);
multiply(10);  // 30

Python:

from functools import partial

# 使用 partial(偏应用)
def add(a, b):
    return a + b

add3 = partial(add, 3)
add3(5)   # 8
add3(10)  # 13

# 手动柯里化装饰器
def curry(fn):
    import inspect
    arity = len(inspect.signature(fn).parameters)

    def curried(*args):
        if len(args) >= arity:
            return fn(*args)
        return lambda *more: curried(*args, *more)

    return curried

@curry
def sum3(a, b, c):
    return a + b + c

sum3(1)(2)(3)    # 6
sum3(1, 2)(3)    # 6

Rust:

// Rust 不原生支持柯里化,但可以手动实现
fn add(a: i32) -> impl Fn(i32) -> i32 {
    move |b| a + b
}

let add3 = add(3);
add3(5);   // 8
add3(10);  // 13

// 宏实现柯里化
macro_rules! curry {
    ($name:ident, $first:ident : $fty:ty, $($rest:ident : $rty:ty),* -> $ret:ty, $body:expr) => {
        fn $name($first: $fty) -> impl Fn($($rty),*) -> $ret {
            move |$($rest),*| $body
        }
    };
}

Clojure:

;; Clojure 不自动柯里化,但可以手动实现
(defn curry2 [f]
  (fn [a]
    (fn [b]
      (f a b))))

(def add (curry2 +))
((add 3) 5)  ;; 8

;; 更实用的方式:使用 partial
(def add3 (partial + 3))
(add3 5)   ;; 8
(add3 10)  ;; 13

;; partial 可以部分应用任意数量参数
(def add-1-2 (partial + 1 2))
(add-1-2 3)  ;; 6

4.5 偏应用(Partial Application)

偏应用是固定函数的部分参数,产生一个接受剩余参数的新函数。

4.5.1 柯里化 vs 偏应用

特性柯里化偏应用
参数传递一次一个一次可传多个
返回函数每次返回单参数函数返回接受剩余参数的函数
灵活性更灵活更直接

4.5.2 实用偏应用

JavaScript:

// 日志函数的偏应用
const log = (level, timestamp, component, message) =>
  `[${timestamp}] [${level}] [${component}] ${message}`;

// 固定级别和组件
const infoLog = (component) => (message) =>
  log('INFO', new Date().toISOString(), component, message);

const authLog = infoLog('AuthService');
authLog('User logged in');   // [2026-01-01T...] [INFO] [AuthService] User logged in

// 事件处理偏应用
const handleEvent = (eventType, handler) => (event) => {
  if (event.type === eventType) handler(event);
};

const handleClick = handleEvent('click', (e) => {
  console.log('Clicked at', e.clientX, e.clientY);
});

document.addEventListener('click', handleClick);

Python:

from functools import partial

# HTTP 客户端的偏应用
def http_request(method, base_url, path, headers=None):
    url = f"{base_url}{path}"
    headers = headers or {}
    return {'method': method, 'url': url, 'headers': headers}

# 创建专用请求函数
api_request = partial(http_request, 'GET', 'https://api.example.com')
get_users = partial(api_request, '/users')
get_user = lambda uid: partial(api_request, f'/users/{uid}')()

get_users()         # {'method': 'GET', 'url': 'https://api.example.com/users', ...}
get_user(42)        # {'method': 'GET', 'url': 'https://api.example.com/users/42', ...}

4.6 函数组合的进阶模式

4.6.1 管道(Pipeline)

// 数据处理管道
const pipeline = (...steps) => (input) =>
  steps.reduce((data, step) => step(data), input);

// 用户注册处理管道
const processRegistration = pipeline(
  validateInput,
  normalizeEmail,
  checkDuplicate,
  hashPassword,
  createUserRecord,
  sendWelcomeEmail
);

const result = await processRegistration({
  email: ' [email protected] ',
  password: 'secret123',
  name: 'Alice'
});

4.6.2 组合子(Combinator)

// 组合子:通过组合产生新行为的函数

// identity
const I = (x) => x;

// constant
const K = (x) => (_y) => x;

// substitution
const S = (f) => (g) => (x) => f(x)(g(x));

// flip
const flip = (f) => (a) => (b) => f(b)(a);

// on
const on = (f) => (g) => (a) => (b) => f(g(a))(g(b));

// 使用:按长度比较字符串
const compareByLength = on((a) => (b) => a - b)(s => s.length);
compareByLength('abc')('de');  // 1 (3 - 2 = 1)

4.6.3 点自由风格(Point-Free Style)

-- 有点(显式参数)
isEven :: Int -> Bool
isEven x = x `mod` 2 == 0

-- 无点(Point-free)
isEven :: Int -> Bool
isEven = (== 0) . (`mod` 2)

-- 有点
sumOfSquares :: [Int] -> Int
sumOfSquares xs = sum (map (^2) xs)

-- 无点
sumOfSquares :: [Int] -> Int
sumOfSquares = sum . map (^2)
// JavaScript 中的 point-free
// 有点
const getActiveUserNames = (users) =>
  users.filter(u => u.active).map(u => u.name);

// 无点(使用 Ramda)
const getActiveUserNames = R.pipe(
  R.filter(R.prop('active')),
  R.map(R.prop('name'))
);

4.7 业务场景

4.7.1 中间件系统

// Express/Koa 风格的中间件(函数组合的典型应用)
const compose = (...middlewares) =>
  (context) => {
    let index = -1;
    const dispatch = (i) => {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'));
      index = i;
      const fn = middlewares[i];
      if (!fn) return Promise.resolve();
      return Promise.resolve(fn(context, () => dispatch(i + 1)));
    };
    return dispatch(0);
  };

// 纯函数中间件
const logging = async (ctx, next) => {
  console.log(`[${ctx.method}] ${ctx.path}`);
  await next();
};

const auth = async (ctx, next) => {
  if (!ctx.headers.authorization) throw new Error('Unauthorized');
  ctx.user = decodeToken(ctx.headers.authorization);
  await next();
};

const cors = async (ctx, next) => {
  ctx.headers['Access-Control-Allow-Origin'] = '*';
  await next();
};

const app = compose(cors, logging, auth);

4.7.2 数据转换管道

# ETL 管道使用函数组合
from functools import reduce
from datetime import datetime

def compose(*fns):
    return lambda x: reduce(lambda acc, fn: fn(acc), fns, x)

# 各步骤都是纯函数
def parse_csv(raw_data):
    return [line.split(',') for line in raw_data.strip().split('\n')]

def skip_header(rows):
    return rows[1:]

def parse_dates(rows, date_col=2):
    return [
        [*row[:date_col], datetime.strptime(row[date_col], '%Y-%m-%d'), *row[date_col+1:]]
        for row in rows
    ]

def filter_valid(rows):
    return [row for row in rows if row[3] > 0]  # amount > 0

def summarize(rows):
    from collections import defaultdict
    summary = defaultdict(float)
    for row in rows:
        summary[row[1]] += float(row[3])
    return dict(summary)

# 组合管道
process_data = compose(
    parse_csv,
    skip_header,
    lambda rows: parse_dates(rows),
    filter_valid,
    summarize
)

result = process_data(raw_csv_data)

4.8 注意事项

注意事项说明
Point-free 过度适度使用,过于隐晦降低可读性
柯里化的性能多次函数调用可能有开销
参数顺序柯里化时参数顺序很重要,数据参数应最后
this 绑定JavaScript 中注意箭头函数和 this
类型推断某些语言中高阶函数影响类型推断

4.9 小结

要点说明
一等公民函数可赋值、传递、返回、存储
高阶函数接收或返回函数的函数
函数组合f ∘ g,将函数串联成管道
柯里化多参数函数 → 多个单参数函数
偏应用固定部分参数,返回新函数

扩展阅读

  1. 《Mostly Adequate Guide to FP》 — 第 5 章 Composition
  2. Ramda.js 文档 — 函数式工具库
  3. Functional-Light JS — 第 3 章 Managing Function Inputs
  4. Haskell 函数组合 — Haskell Wiki

下一章05 Map/Filter/Reduce — 函数式数据处理的核心操作