Skip to article frontmatterSkip to article content

最后要介绍的是元编程. 这个模型的核心理念是代码即数据.

“代码即数据”的理念由冯·诺依曼早在1946年提出, 冯·诺依曼机的设计也基于这一理念. 因此, 针对元编程的实践实际上由来已久.

Meta Programming的不同含义

根据[1], "元编程"其实代表很多不同的应用方向.

异质元编程

异质元编程, 是指meta程序和应用程序不是用同一种语言编写的.

异质元编程应用方向包括:

然而除非专门研究语言设计, 异质元编程在日常应用开发中, 我们很少接触.

同质元编程

同质元编程, 是指meta程序和应用程序是同一种语言编写的.

Generalization

同质化元编程的一个常见的应用方向是generalization, 即让代码变得更通用, 譬如各种语言中的template系统, 可以用来编写generic数据容器和复杂的算法.

generalization.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <cstddef>

template<typename T, size_t N>
class Stack {
private:
	T data[N];
	size_t top = 0;

public:
	void push(const T& item) {
		if (top < N) data[top++] = item;
	}

	T pop() {
		return top > 0 ? data[--top] : T{};
	}

	bool empty() const { return top == 0; }
	size_t size() const { return top; }
};

int main() {
	Stack<int, 10> intStack;
	Stack<float, 20> floatStack;
};

开发meta-program

同质化元编程另一个应用方向是编写meta-program. 譬如lisp, c, rust等语言中的macro, 都是在原有应用程序的基础上, 提供了扩展语言, 编译时运行, 动态生成代码等能力.

lisp macro
c macro
rust macro
macro.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
;; 定义一个宏, 运行后自动生成一个全局变量和它的getter, setter
(defmacro defproperty (name)
  (let ((getter (intern (format nil "GET-~A" name)))
		(setter (intern (format nil "SET-~A" name)))
		(var (intern (format nil "*~A*" name))))

	`(progn
	   (defvar ,var nil)
	   (defun ,getter () ,var)
	   (defun ,setter (value) (setf ,var value)))))

(defproperty username)
(set-username "Alice")
(print (get-username)))

参考[2]中的第10章

关注点分离

一些框架使用了meta-programming的技巧, 分离关注点, 把业务和框架代码完全隔离. 譬如spring MVC/boot中的应用.

xController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    @Cacheable("users")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }

    @PostMapping
    @Transactional
    @Valid
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
}

@Service
@Transactional
public class UserService {
    @Autowired
    private UserRepository repository;

    @Retryable(maxAttempts = 3)
    public User findById(Long id) {
        return repository.findById(id).orElse(null);
    }

    public User save(User user) {
        return repository.save(user);
    }
}

在使用spring boot之后, 在代码库中除了注解, 你几乎看不到任何关于spring的代码. 而注解本身没有任何逻辑效用.

反射和元信息

另外常见的两种能力是反射元信息. 反射是在运行时能够获取当前代码结构(譬如类上的结构信息, 函数结构信息, 上下文信息), 甚至修改这些结构和行为(譬如修改和扩充类定义). 而元信息是可以直接把各种文档和描述直接附在程序中, 甚至在runtime下都能直接访问.

dynamicly_load_json.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import json
from dataclasses import dataclass, fields, is_dataclass
from typing import List, get_origin, get_args

@dataclass
class Address:
    street: str
    city: str

@dataclass
class Person:
    name: str
    age: int
    address: Address
    tags: List[str]

# 通用的from_json方法, 使用反射的技巧来动态加载json到dataclass中.
def from_json(cls, json_dict):
    """这里的docstring就是元信息, 你可以通过from_json.__doc__在runtime下得到这些元信息"""

    if not is_dataclass(cls):
        raise TypeError(f"{cls} is not a dataclass")

    init_kwargs = {}
    
    for field in fields(cls):  # 遍历给定class的每一个字段
        field_value = json_dict.get(field.name)

        if field_value is None:
            continue

        field_type = field.type
        origin = get_origin(field_type)

        # 如果字段是嵌套的dataclass, 递归调用from_json进行处理
        if is_dataclass(field_type):
            init_kwargs[field.name] = from_json(field_type, field_value)

        # 如果字段是个list, 尤其是list of dataclasses, 要进行递归处理
        elif origin is list or origin is List:
            item_type = get_args(field_type)[0]
            if is_dataclass(item_type):
                init_kwargs[field.name] = [from_json(item_type, item) for item in field_value]
            else:
                init_kwargs[field.name] = field_value

        else:
            init_kwargs[field.name] = field_value

    return cls(**init_kwargs)

# Example JSON
json_str = '''
{
    "name": "Bob",
    "age": 40,
    "address": {
        "street": "123 Main St",
        "city": "Metropolis"
    },
    "tags": ["friend", "colleague"]
}
'''

data = json.loads(json_str)
person = from_json(Person, data)

# 使用反射遍历person的字段.
for field in fields(person):
    value = getattr(person, field.name)
    print(f"Field: {field.name}, Type: {field.type}, Value: {value}")

每种语言通常都会提供一种或多种相关能力.

什么时候使用meta Programming

什么时候使用meta-programming呢? 需要分情况讨论.

其中最常用的包括generalization, reflection和meta-data.

编写meta-program相对不常用, 且需细分:

最后, 若代码库引入meta-programming技巧, 需大量测试覆盖. 升级编译器或解释器版本时, 甚至需要全量测试以确保业务系统行为不变.


Footnotes
  1. Meta-Programming and Model-Driven Meta-Programming Development, 作者 Vytautas Štuikys, Robertas Damaševičius, 2013年出版

  2. ANSI Common Lisp, 作者 Paul Graham, 1995年出版