本文来自飞书aPaaS Growth研发团队成员,已获得ELab授权发布。
aPaaS Growth团队专注于用户可感知的、宏观层面的aPaaS应用构建流程,以及租户、应用治理等产品路径。 致力于为aPaaS平台打造流畅的“应用交付”流程和体验,完善与应用建设相关的生态系统,增强应用建设的便利性和可靠性,提高应用的整体性能,从而帮助应用的用户增长。 aPaaS,与基础团队共同推动aPaaS在企业内外的落地和效率提升。
背景 1:定义 SSOT
问:在 SwiftUI 上下文中llvm 游戏引擎支持更多语言,单一事实来源 (SSOT) 的含义是什么?
A:使用SwiftUI,你既可以务实地编写代码,也可以使用设计工具来编辑UI,这也会导致SwiftUI代码被修改。 本质上,您只有源代码,没有单独的设计文件(即 nib 或 Storyboard[1](2016)),这意味着您的 UI 设计和处理 UI 的代码不可能不同步(以前的 nib 文件或故事板就是这种情况)。
背景2:什么是程序和语言? 什么是计算机程序?
计算机程序是编程语言[2]中供计算机[3]执行[执行]((计算“执行”))的序列或指令集。
什么是编译型语言
%E7%B7%A8%E8%AD%AF%E8%AA%9E%E8%A8%80
编译语言(英语:Compiled language)是一类通过编译器实现的编程语言[4][5]。 它不像解释语言[6],解释器逐句执行代码。 相反2d素材,编译器首先将代码[7]编译为机器代码[8],然后执行它。 理论上,任何编程语言都可以编译或直译。 它们之间的区别仅与程序的应用有关。
什么是分析语言
%E7%9B%B4%E8%AD%AF%E8%AA%9E%E8%A8%80
解释性语言(英语:Interpreted language)是编程语言的一种[9]。 这种类型的编程语言直接逐句执行代码。 它不需要由编译器[11]编译成机器代码[12],然后像编译语言[10]一样执行。 这种编程语言需要使用解释器[13]。 在执行期间,代码被动态地逐句解释(interpret)为机器代码,或者是已经预编译为机器代码的子程序[14],然后执行。
编程语言应该遵循的数学模型以及编程语言的发展历史
首先,每种语言至少应该具有图灵完备性。 至于什么是图灵完备性,可以参考我之前的分享:证明JS和TS类型编程是图灵完备的[15],简而言之,一种语言只要能实现三种基本功能和三种基本组合,那么它是图灵完备的,绝对可以表达其他图灵完备语言可以表达的逻辑。 注意,这里所说的是纯粹的逻辑,从数学的角度来看。 看来实现一个功能,输入和输出属于同一个域。 如果有新域,则必须在上下文中添加输入。 例如,设备/文件IO需要机器指令的支持。
编程语言都是形式语言(Formal Language[16]),而形式语言的研究早在计算机出现之前就由语言学家提出了。
形式语言的首次使用被认为是 Gottlob Frege[17] 1879 年的 Begriffsschrift[18],意思是“概念写作”,它描述了一种“以算术语言为模型的形式语言,用于纯粹的思考。”([2 ])
需要注意的是,图灵是否完备与语法是否简单并无直接关系。 对于语法非常简单但是图灵完备的语言,可以参考: ,一些我们认为表达力还不错的常用语言其实并不是图灵完备的。 完整的(比如微信的WXML,或者UIDL),原因很简单,就是它们无法实现部分递归函数必须具备的“最小化”操作,即无法实现while循环。
当我们回顾计算机语言的发展过程时,我们会发现计算机语言的语法是一个从简单到复杂的过程。 最初,我们用打孔卡来表示程序()3D动画,程序本质上是一串数字。 后来,机器被发明了。 代码基本上就是数字和一些助记词一一对应,然后指令后面跟着内存地址来表达程序。 后来汇编增加了“过程”的概念,后来有了像Lisp这样的语言,增加了“表达式”的概念。 后来Lisp的方言越来越多,增加了“函数”和“语句”。 概念。 后来,我们有了更正式的现代语言 C,它添加了块和控制流等概念。 后来我们有了Java这样的面向对象语言,它增加了面向对象设计相关的概念(类、接口、封装、多态、继承),现在我们有了ES 2022、Swift、Golang、Rust和还有很多。 语言llvm 游戏引擎支持更多语言,针对不同的领域,它们有不同的语法和特性。
回顾整个过程,是一个从简单到复杂、从易学到难学、表达力从弱到强、从不实用到实用、从易解析到难解析的过程。 过程。
以下纯属个人观点:一种语言很难面面俱到。 每种语言通常都有其适用的场景,常常面临类似于三维悖论的场景[19](当然,可以引入更多维度,形成四五六七八十九悖论)。 关于...),例如:
图片.png
比如Javascript这样的分析型语言,语法比较简单,表现力比较强(支持面向对象编程、async wait语法、function as First Citizen……),但是它们的性能会比较弱,因为你无法控制锁。 ,如果没有锁,需要引入eventloop,造成性能消耗; 如果没有内存控制,就需要引入GC,造成性能消耗。
另一个例子是 Rust 中内存所有权的引入。 语言的学习复杂度骤然增加,但在性能场景中也能在性能和表现力方面做得更好。
背景 3:简单的分析语言实现
话虽如此,在正式介绍 XCode + SwiftUI 之前,我需要先介绍一下一种语言是如何解析的。 我们以最简单的语言之一Lisp为例,用最简单的自循环解析器来讲解。
图片.png
假设我们有一个简单的 Lisp 程序:
// 语义为:(60 * 9 / 5) + 32
(+ (* (/ 9 5) 60) 32)
那么解析器程序的第一步会执行tokenize程序,结果是:
[
"(",
"+",
"(",
"*",
"(",
"/",
"9",
"5",
")",
"60",
")",
"32",
")"
]
第二步是执行解析并将 token 列表转换为 AST:
[
"+",
[
"*",
[
"/",
9,
5
],
60
],
32
]
第三步是解析 AST。 解析过程本质上是一个深度递归的自下而上的评估过程。 例如,上述AST的评估过程如下:
// 输入
[
"+", // <- 第一层指针,发现子属性是数组,先求数组的值
[
"*", // <- 第二层指针,发现子属性是数组,先求数组的值
[
"/", // <- 第二层指针,没有子属性是数组了,不需要递归了,在这里求第一次值
9,
5
],
60
],
32
]
// 第一遍求值
[
"+",
[
"*",
1.8,
60
],
32
]
// 第二遍求值
[
"+",
108,
32
]
// 第三遍求值
140
完整的Demo请参考:
function interp(x, env) {
env = env || g;
if (typeof x === "string") { // symbol
return env.find(x)[x];
}
else if (!Array.isArray(x)) { // constant literal
return x;
}
else if (x[0] === "quote") { // (quote exp)
let exp = x[1];
return exp;
}
else if (x[0] === "if") { // (if test conseq alt)
let test = x[1], conseq = x[2], alt = x[3];
let exp = interp(test, env) ? conseq : alt;
return interp(exp, env);
}
else if (x[0] === "define") { // (define symbol exp)
let symbol = x[1], exp = x[2];
env[symbol] = interp(exp, env);
}
else if (x[0] === "set!") { // (set! symbol exp)
let symbol = x[1], exp = x[2];
return (env.find(symbol)[symbol] = interp(exp, env));
}
else if (x[0] === "eval") { // custom shenanigans
let exp = interp(x[1], env);
return interp(exp, env);
}
else if (x[0] === "lambda") { // (lambda (symbol...) body)
let parms = x[1], body = x[2];
return makeProc(parms, body, env);
}
else {
let proc = interp(x[0], env);
let args = x.slice(1).map(exp => interp(exp, env));
if (typeof proc !== "function") {
throw new Error("Expected function, got " + (proc || "").toString());
}
return proc.apply(proc, args);
}
}
同时,如果我们稍微添加一个包装器,我们就可以得到这个程序的调用堆栈。
let stack = [];
function wrap(func) {
return function(...args) {
stack.push(args[0]);
let resp = func(...args);
stack.pop();
return resp;
};
}
interp = wrap(interp);
好像到这里,你知道什么是stackoverflow异常吗? 本质上,我们的程序在 AST 上进行递归,并在递归过程中不断将内容压入堆栈。 如果程序写得不好,堆栈空间就会溢出。
怎么样,这段代码是不是感觉很熟悉? 如果我们打开kunlun-fe的parseComponentMeta.ts文件,你会发现它也是一个自循环解析器:
export function parseComponentMeta(
meta: ComponentMeta,
components: Components = {},
...
): JSX.Element {
const { name, type, children, events, selectors: selectorMeta } = meta;
...
const { props = {} } = meta;
const { key: propKey } = props;
if (propKey === undefined) {
// props = { ...props, key: name };
props.key = name;
}
props.__component_name__ = name;
let normalizedChildren = null;
if (typeof children === 'string') {
normalizedChildren = children;
} else if (Array.isArray(children) && children.length > 0) {
normalizedChildren = children. map ( ( childMeta ) =>
parseComponentMeta (
childMeta,
components,
connect,
stateKey,
payload,
componentCache,
selectors,
decorator,
),
);
}
if (isHostComponent(type)) {
return createElement(type, props, normalizedChildren);
}
let ComponentType = deepGet(components, type) as React.ComponentType;
if (ComponentType === undefined) {
window.console.error(type, ' is not found in components:', components);
ComponentType = NotFound;
}
if (events !== undefined && connect === undefined) {
throw new DangerousCustomErrorWithoutSensitiveMessage({
label: 'page-meta-engine',
message: '"connect" is required when "events" passed.',
});
}
if (selectorMeta !== undefined && connect === undefined) {
throw new DangerousCustomErrorWithoutSensitiveMessage({
label: 'page-meta-engine',
message: '"connect" is required when "selectors" passed.',
});
}
if (isInBlacklistOfConnect(type) || connect === undefined) {
return createElement(ComponentType, props, normalizedChildren);
}
// 做了一些 redux wrapping 相关的东西
return ...
}
换个角度思考,昆仑的UI Meta或者后续的UIDL直接定义了一套AST,然后实现了一个自循环的解析器。
延伸思考:
如果我现在需要使用UI Meta或者UIDL来实现一个无限加载列表组件,可以实现吗? 如果你扩展 UI Meta 而不实现自定义 React 组件,你会引入什么样的 Meta 属性? 这些属性的原子操作是什么? 你如何解析它?
如果我想在UI Meta中添加条件渲染功能,应该如何实现?
如果我们实现一个JS解析器会不会很困难? 事实上,这并不是很难。 如果不考虑效率,我们只需要1000行代码就可以实现ES5的AST的eval,从而实现ES5的语义。
我们的 ES AST 比 Lisp 细节丰富得多,也更容易理解。 例如,对于同一个表达式,ES表达式是这样的:
#/要点/c40c85b756de9a4e10fb5bfe668fa000/ff2ab3d89b593035af82b97cddf72a68910dcab9
{
"type": "File",
},
"errors": [],
"program": {
"type": "Program",
},
"sourceType": "module",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
},
"expression": {
"type": "BinaryExpression",
},
"left": {
"type": "BinaryExpression",
},
"left": {
"type": "BinaryExpression",
},
"left": {
"type": "NumericLiteral",
},
"extra": {
"rawValue": 60,
"raw": "60"
},
"value": 60
},
"operator": "*",
"right": {
"type": "NumericLiteral",
},
"extra": {
"rawValue": 9,
"raw": "9"
},
"value": 9
}
},
"operator": "/",
"right": {
"type": "NumericLiteral",
},
"extra": {
"rawValue": 5,
"raw": "5"
},
"value": 5
},
"extra": {
"parenthesized": true,
"parenStart": 0
}
},
"operator": "+",
"right": {
"type": "NumericLiteral",
},
"extra": {
"rawValue": 32,
"raw": "32"
},
"value": 32
}
}
}
],
"directives": []
},
"comments": []
}
XCode + SwiftUI 界面设计经验