Skip to content

Commit a63f94d

Browse files
committed
docs: synced via GitHub Actions
1 parent 0a123cc commit a63f94d

File tree

4 files changed

+330
-6
lines changed

4 files changed

+330
-6
lines changed

src/theory/mathematical-kernel-of-model-driven-architecture.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
### 模型驱动架构的数学内核:统一生成与演化的 Y = F(X) ⊕ Delta 不变式
1+
# 模型驱动架构的数学内核:统一生成与演化的 Y = F(X) ⊕ Delta 不变式
22

33
**摘要:** 模型驱动架构(MDA)通过提升抽象层次与自动化来应对软件复杂性。然而,传统MDA在实践中面临“往返工程”与“胖模型”等挑战,这些问题的解决一般依赖于工程经验,缺少完备的数学理论的指导。本文探讨一种基于(广义)可逆计算理论的架构思想,它引入“差量(Delta)”作为具备代数属性的基本构造单元,并提出`Y = F(X) ⊕ Delta`这一构造不变式。该框架旨在统一软件的生成与演化过程,为解决传统MDA的固有问题提供一种新的范式,并揭示软件构造过程背后潜在的数学结构。
44

5-
#### **1. 模型驱动架构(MDA)的贡献与局限**
5+
## **1. 模型驱动架构(MDA)的贡献与局限**
66

77
模型驱动架构的核心是将模型作为软件开发过程的首要产物。其经典的流程——从计算无关模型(CIM)到平台无关模型(PIM),再到平台相关模型(PSM),最后生成代码。
88

@@ -18,7 +18,7 @@
1818

1919
但是这里的 `+ManualAdjustment`一般是一种人工介入的操作过程,比如手工对生成代码修改,难以通过规则自动化完成。这种行为破坏了模型作为“唯一真相来源”的原则,导致模型与代码的状态不一致。
2020

21-
#### **2. 构造单元的重定义:以“差量(Delta)”为核心**
21+
## **2. 构造单元的重定义:以“差量(Delta)”为核心**
2222

2323
**(广义)可逆计算理论**提供了一种新的理论视角,它建议对软件的基本构造单元进行重新定义。
2424

@@ -27,7 +27,7 @@
2727

2828
这一视角的转变,意在将软件的“初始构造”和“后续演化”两个过程统一在同一个概念之下。这与版本控制系统(如Git的Commit)或事件溯源(Event Sourcing)的思想存在关联,但其应用目标是程序的**结构本身**,而非文本或运行时状态。
2929

30-
#### **3. Delta导向架构:基于差量代数的不变式**
30+
## **3. Delta导向架构:基于差量代数的不变式**
3131

3232
基于“一切皆差量”的公理,传统MDA的构造公式可被一个更通用的公式所取代:
3333

@@ -42,7 +42,7 @@
4242

4343
为实现此框架,需要在不同的程序结构空间中定义相应的``运算规则。例如,对于树状结构的XML或JSON,可定义一种`x-extends`的差量合并算法,满足结合律但不满足交换律;对于具有复杂依赖图的Java语言结构,则可以通过引用id将循环关联断开,将图投影为树结构。
4444

45-
#### **4. 递归分解:应对复杂性与“胖模型”问题**
45+
## **4. 递归分解:应对复杂性与“胖模型”问题**
4646

4747
`Y = F(X) ⊕ Delta`这一不变式的一个关键特性在于其**递归应用**的能力,这为处理复杂系统和MDA的“胖模型”问题提供了一种结构化的解决方案。
4848

@@ -73,7 +73,7 @@
7373

7474
这意味着一个通用的代码生成器可以通过与一个特定的“特性差量包”进行合并,演化为一个专用的生成器。这为元编程工具的复用和演进提供了理论上的可能性。
7575

76-
### **5. 元模型、差量与生成式构造**
76+
## **5. 元模型、差量与生成式构造**
7777

7878
本文提出的数学框架,在元模型(Metamodel)层面同样适用,并能与现代AI技术无缝结合。
7979

src/theory/nop-platform-learning-cost.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,3 +807,9 @@ Nop平台的认知成本被普遍误解,根源在于我们习惯用**传统工
807807

808808
**最终,Nop平台的价值不在于让你学得更少,而在于让你的每一次学习都更有价值——从重复的记忆负担,走向创造性的架构自由。**
809809

810+
811+
这篇文章远不止是 Nop 平台的宣传文档,而是一次对现代软件复杂性治理方式的深刻反思。它主张:
812+
813+
不要隐藏复杂性,而要显式化它,并用统一的数学结构去驯服它。
814+
815+
强调“显式优于隐式”: 这是一个强大的软件工程原则,Nop似乎始终如一地应用了它(_dump、XDef)。

src/theory/prefix-guided-syntax.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# 利用前缀引导语法实现DSL的灵活嵌入
2+
3+
Nop平台在做DSL语法设计的时候,一个很重要的观念是分层语法设计,即我们可以把多种风格的DSL混合在一起使用,但是它们之间具有良好的形式分隔边界,在按照高层的DSL语法进行解析的时候,无需考虑低层DSL语法的解析要求。典型的,JSX语法不符合这一要求。JSX语法可以看作是普通js语法和XML语法的混合,但是jsx语法解析器在Lexer中统一识别js和XML的token,并把语法元素解析到一个统一的AST语法树中,并不能先按照XML解析得到一个总体结构,然后再针对局部解析js或者整体先按照js解析,然后再针对局部按照XML解析。
4+
5+
## 一. 在XML中嵌入其他语法
6+
7+
在Nop平台中,XLang语言采用了XML为基础语法形式,可以通过XML标签来标记出特定语法部分。例如
8+
9+
```xml
10+
<c:script>
11+
let x = 3;
12+
...
13+
</c:script>
14+
15+
<c:script lang="groovy">
16+
// 调用groovy脚本引擎
17+
</c:script>
18+
```
19+
20+
`<c:script>`标签内部可以执行XScript脚本(语法类似TypeScript),也可以通过lang属性来选择使用groovy等其他脚本引擎,从而支持不同的程序语法。这里的关键点在于,在XML层面,`<c:script>`只是一个普通的XML标签,它的内容只是一个普通的字符串文本,只有局部的`<c:script>`标签的处理器需要具有Groovy语法的知识,而作为整体的XPL模板语言并不需要有任何Groovy的知识。
21+
22+
类似于Lisp语言中的宏,XLang语言的`<c:script>`本质上也只是一种宏标签,它在编译期会自动运行,执行具体的解析逻辑,并返回解析得到的抽象语法树。这一做法可以被很简单的推广到其他DSL语法格式上,例如在前台页面中使用的布局语法
23+
24+
```xml
25+
<ui:Form>
26+
<layout>
27+
fieldA fieldB
28+
fieldC
29+
</layout>
30+
</ui:Form>
31+
```
32+
33+
`<ui:Form>`表示根据当前对象的元数据生成一个表单,这个表单具有两行,第一行显示fieldA, fieldB,第二行显示fieldC,具体使用的控件根据meta文件中配置的字段类型来自动选择。在实现层面,ui:Form会在编译期自动解析Layout布局对应的DSL语法,并生成LayoutModel对象,然后结合meta信息自动生成组件代码等。
34+
35+
基于这里的机制,其实我们很容易实现JetBrains公司的MPS产品中所提出的Projectional Editor的概念。所谓的可视化编辑器,其产生的输出可以看作是使用某种DSL来定义的模型,而DSL语法解析后的结果都是AST抽象语法树,而在任意的AST节点,我们都可以重新选择一个DSL语法形式,它用于实现对本AST节点信息的一个定制的表达。
36+
37+
## 二. 在JavaScript中嵌入其他语法
38+
39+
在XScript脚本语言中,我们使用模板字符串语法来嵌入XML。与JavaScript中不同的是,XScript脚本语言不会自动识别嵌入的表达式,例如
40+
41+
```javascript
42+
let x = xpl `<c:if test="${condition}">...</c:if>`
43+
```
44+
45+
在标准的js语法中,`${condition}`会被自动按照表达式语法进行解析,而在XScript中,整个模板字符串会被作为字符串来解析,通过重复的````字符来表示转义。
46+
47+
在我看来,js模板字符串的设计中,自动识别`${expr}`是一个错误,它破坏了一种自然的分层语法设计,使得模板字符串内的DSL语法与外部的JavaScript语法混杂到了一起,造成了解析和处理层面一系列的不便,同时也影响了内部DSL语法的直观性。
48+
49+
在XScript的实现中,模板字符串语法被定义为编译期宏函数的调用,即xpl为编译期自动执行的宏函数,它的具体实现如下
50+
51+
```java
52+
@Macro
53+
public static Expression xpl(IXLangCompileScope scope, CallExpression expr) {
54+
String tpl = getTemplateLiteralArg(expr);
55+
if (StringHelper.isBlank(tpl))
56+
return Literal.nullValue(expr.getLocation());
57+
58+
XNode node = XNodeParser.instance().forFragments(true).parseFromText(expr.getArgument(0).getLocation(), tpl);
59+
return scope.getCompiler().parseTagBody(node, scope);
60+
}
61+
```
62+
63+
在编译期会执行xpl宏函数,传入模板字符串表达式所对应的AST节点。这一功能类似于C#中LinQ表达式的处理,只是宏函数是一个更通用的机制。本质上,它的作用也类似于Lisp语言中的宏。
64+
65+
这一机制可以用于多种DSL的嵌入,比如
66+
67+
```javascript
68+
let p = xpath `/a/a[@id=a]`
69+
```
70+
71+
表示在编译期会解析xpath语法,并返回一个XPath对象赋值给变量p。
72+
73+
如果要在XScript中嵌入类似LinQ的SQL语法,可以使用
74+
75+
```javascript
76+
function myFunc(x,y){
77+
return x + y;
78+
}
79+
let obj = ...
80+
let {a,b} = linq `
81+
select sum(x + y) as a , sum(x * y) as b
82+
from obj
83+
where myFunc(x,y) > 2 and sin(x) > cos(y)
84+
`
85+
```
86+
87+
在特殊定制的linq宏函数中,我们可以非常精确的分析出myFunc为外部环境中调用的函数,而obj为外部环境中定义的变量,实现SQL语法和JavaScript语法的自然融合。
88+
89+
JavaScript内置的模板字符串功能在XScript中可以通过tpl宏函数调用来实现
90+
91+
```javascript
92+
let x = tpl `sss ${myVar}`
93+
```
94+
95+
## 三. 前缀引导语法
96+
97+
上一节中介绍的xpl宏函数机制可以看作是前缀标识 + 多行文本字符串这种形式,前缀标识被解释为处理函数,而多行文本字符串具有内外两种结构,外部结构就是普通的多行文本,仅需要对特殊字符````进行识别转义即可,其他的回车换行反斜杠等字符都不需要进行转义。而内部结构就是仅由前缀标识函数所识别的DSL语法。这一形式的优点在于它可以在完全不改变外部程序语法也不需要任何内部DSL知识的情况下,将DSL无缝嵌入到外部程序结构中。我将这一形式称之为前缀引导语法。它可以被看作是DSL设计的一个通用技巧,应用范围非常广泛。
98+
99+
在Nop平台的设计中,我们大量使用了前缀引导语法的设计形式
100+
101+
### 3.1 加密字段
102+
103+
在配置文件中存储密码的时候需要加密,我们约定以`@enc:`为前缀的配置值需要被自动解密。
104+
105+
在ORM模型中如果标注了某个字段需要加密存储,则保存到数据库中时会自动加密并增加`@enc:`前缀,这样在读取的时候可以自动识别是否需要解密,便于在系统运行过程中动态调整加解密设置。
106+
107+
### 3.2 动态配置
108+
109+
在配置文件中的值一般为固定值,但是在灰度发布场景下,我们希望将静态配置扩展为动态配置,对满足某些条件的调用使用配置A,而对其他调用使用配置B。在Nop平台中,我们通过`@switch`前缀来标识动态配置项。例如
110+
111+
```
112+
nop.a.b = @switch: {json格式的业务规则}
113+
```
114+
115+
在调用端使用的时候
116+
117+
```java
118+
static final IReference<String> CFG_XXX = AppConfig.varRef("nop.a.b",String.class);
119+
120+
CFG_XXX.get()
121+
```
122+
123+
会根据switch配置返回动态确定的值。
124+
125+
使用前缀引导语法这种形式来扩展配置项,完全保持了系统原有的key=value配置结构,可以复用此前的界面和存储,只需要在使用端增加一个局部的DynamicReferenc的支持即可。
126+
127+
### 3.3 Redis缓存编码
128+
129+
将Java对象通过JSON序列化保存到Redis缓存中时,在JSON字符串之前增加`@data:DATA_CLASS`前缀,将反序列化所需要的Java类名(可以包含包含泛型信息)和数据一起打包保存到缓存中,便于反序列化后直接得到强类型的Java对象。
130+
131+
### 3.4 IoC配置
132+
133+
在Spring的XML配置中,如果我们希望表达value对应的不是值,而是引用名称,则需要使用一个新的属性名
134+
135+
```xml
136+
<bean>
137+
<property name="myProp" value-ref="xxx" />
138+
</bean>
139+
```
140+
141+
value-ref表示属性myProp的值不是xxx这个字符串,而是它所对应的bean,xxx为bean的引用名称。实际上,为了支持引用的概念,spring引入了ref, value-ref, key-ref等多个额外的结构。而在Nop平台中,我们可以使用`@ref:name`这种形式来统一表达对象引用
142+
143+
```xml
144+
<bean>
145+
<property name="myProp" value="@ref:xxx" />
146+
</bean>
147+
```
148+
149+
在任何需要对象引用的地方都可以直接使用`@ref`来表达,极大简化了领域对象的结构。同时,因为对象属性通过唯一的名称来标识,而不是分裂为 value-ref/value等多个属性名,这也方便了Delta定制(按名称直接覆盖,而不需要考虑多种情况,而不需要考虑多个属性同时存在时的优先级问题)。
150+
151+
Spring号称是完全声明式的依赖注入,但是涉及到IoC容器内部的一些概念的时候,它还是要依赖于明确约定的内部接口,比如为了注入bean的名称,需要实现BeanNameAware接口
152+
153+
```java
154+
interface BeanNameAware{
155+
void setBeanName(String beanName);
156+
}
157+
```
158+
159+
在Nop平台中,我们可以通过`@bean:id`来表示注入bean的名称
160+
161+
```xml
162+
<bean>
163+
<property name="beanName" value="@bean:id" />
164+
</bean>
165+
```
166+
167+
类似的,可以用`@bean:container`来表示注入当前容器等。
168+
169+
170+
基于可逆计算理论设计的低代码平台NopPlatform已开源:
171+
172+
* gitee: https://gitee.com/canonical-entropy/nop-entropy
173+
* github: https://github.com/entropy-cloud/nop-entropy

0 commit comments

Comments
 (0)