跳到主要内容

Nop 平台理论探讨

为避免隐私泄漏,群友问答均以匿名形式组织。另外,为便于阅读和理解,部分问答内容会做相应调整。

要点整理

Unison 是算是一种实现技巧吧,根据 content 得到 hash,从而得到一种类似名称, 但是又能指向结构的东西,本身与版本号类似,只是它基于结构本身, 而不是名称来确定唯一性而已。当我们想消除思维中的含混性的时候, 最简单的做法就是进入相空间描述,将结构在时间轴上的每个点都区分出来, 但是这样会大大增加整体描述的复杂性。

在现有的技术条件下 GraphQL 是一个很好的选择。 本质上是信息分布的改变,原先信息由服务端完全确定, 现在交互信息是服务端和客户端共同决定的, 一部分信息(具体使用哪些字段)这些信息放到了客户端, 也就是说要改变原有信息的组织方式,把 A 和 B 的某种协商作为交互的一部分。

原则上说每个服务(这里指微服务)都不是直接对接的,而是经过某种环境对象交互来发生相互作用的。 所以真正的作用是 A + B + 环境,环境本身可以提供柔性适配的功能, 可以注入外部公共知识、公共规则等。

如果要以演化的、松耦合的方式来看待外部世界,那意味着任何时候你看见的都不是对象的全部, 你只知道你需要知道的信息,而你需要知道别的信息的时候可以看到, 你也可以选择以旧的方式继续看待原有的信息。

Delta 需要一个预定义的坐标系作为参照物,Delta 只有在同一个坐标系下才能进行合并。 如果我们认为 Delta 是针对整个系统的,就会导致预定义的坐标系过于复杂。

实际上需要将整个系统分解为多个 DSL,然后一个 Delta 包中包含多个 DSL Delta。

可逆计算理论有没有可能支持运行期的演化,在用户使用过程中不断完成自我进化

这是一个有趣的问题。先说一下结论:可以,可逆计算可以应用于运行期演化

比如现在 Nop 平台中的设计实际上是延迟编译、即时编译的,如果上线之后我们修改 DSL 模型, 则所有依赖于这个模型的所有模型都会自动失效,下次再访问到这些模型的时候会自动重新加载编译。 例如,如果 NopAuthUser.xmeta 被修改了,则 NopAuthUser.view.xml 和用到 NopAuthUser.view.xml 的所有页面模型也会自动更新。在整个过程中是不需要重新启动系统的, 都是在运行期重新编译即可。SaaS 多租户系统在抽象的层面上看,也可以理解为一个 Delta 定制问题, 每个租户相当于对应于一个 Delta,而且这些 Delta 在运行时的状态空间也是相互隔离的。 实际上,因为 Nop 平台系统化的考虑了 Delta 的构建、分解机制, 相比于其他技术方案它能够更加优雅的处理运行期演化问题。 基于 Nop 平台的设计模式,我们可以实现完全不停机的情况下持续的软件演化。

更为复杂的一个问题是运行期的状态空间是否也能纳入 Delta 的管理范围, 毕竟整个应用系统的完整描述 = 结构 + 状态。答案同样是可以,因为可逆计算是一个完全抽象的理论, 它可以将结构空间 + 状态空间综合在一起,定义一个完整的高维空间, 然后再考虑这个高维空间中的差量化演化(在物理学中,这个高维空间被称为相空间)。 但是从实践的角度上说,考虑状态将导致需要处理的复杂度直线上升, 所以一般情况下我们只会考虑结构空间中的 Delta,而忽略状态空间。

具体举一个例子,比如说我们现在要对一个机器人进行 Delta 改进。 对这个机器人的完整描述肯定是包括它的结构,同时还要包括这些结构所处的状态, 比如说它处于高速的运动状态中,我们需要描述它的各个部分的相对速度、加速度、角速度,甚至压力、温度等, 但是我们一般情况下不会在机器人的工作模式下对它进行改装,而是要把它转入到静息模式下,也就是某种非激活的模式, 工作模式下的各种信息对于改装而言是不相关的、可忽略的。少数极端的情况下,我们需要在行驶的火车上换车轮, 那么基本的做法大概是:先使用时间静止技术,在局部冻结时间线,然后将部分需要保持的状态序列化到存储中, 然后修改结构,再次加载状态,一切就绪后再恢复时间演化。 在 Delta 修正所应用的过程中,时间是静止的,各类状态是冻结的,状态本身也是可以进行 Delta 修正的数据。 比如说我们使用了某种锁机制,直接阻止外部操作,从外部使用者来看就是时间静止。 更复杂的技术涉及到快照复制,然后在平行宇宙中不断复制 Delta 进行追账,直到对齐到某个时间点,在短时间内静止时间, 完成时间线切换。关于时间静止的一个有趣应用, 可以参见文章Paxos 的魔法学研究报告

对状态进行 Delta 修正的复杂度在于它并不是仅仅由对象自身决定的, 比如我们的机器人正在和别的机器人进行激烈的战斗,它的状态本质上是与对手相互作用导致的结果, 我们如果需要复现这个状态,必须要考虑到能量守恒、动量守恒、角动量守恒等一系列物理约束, 我们无法单方面的从机器人个体的角度出发复现它的状态,而是必须要考虑所有与它交互的客体的情况, 需要考虑环境与机器人之间的相互影响,这导致复现状态或者修改状态是一件非常复杂的事情。

根据上面的讨论,我们如果希望在运行期实现演化,基本的做法有两种:

  1. 分离结构和状态,比如,微服务本身就是结构而已,它本身不包含业务状态,所以可以随时启停;
  2. 定义激活和非激活两种模式,切换到非激活模式下完成结构修订;

如果发散一下考虑生物界的演化问题。我们可以把 DNA 看作是某种承载信息的 DSL, 生物的成长过程对应于 Generator,它根据 DSL 的信息并结合环境信息塑造出生物的本体。 同时生物在应对具体问题挑战的时候,还可以利用外部的各种 Delta。 比如人可以穿上潜水服这个 Delta,获得在水中活动的能力,可以加上不同厚度的衣服, 获得在极寒和极热地区活动的能力。而一般的动植物能够加装的 Delta 很少, 比如飞鸟,如果它在水中长时间潜水,飞行用的翅膀反而是一种阻碍。

那针对函数(业务处理逻辑)有可能采用差量机制么?如果可以,那是采用怎样的实现方案,又会面临哪些挑战呢?

函数应该就是 Generator

可逆计算的计算模式 App = Tree x-extends Tree<Tree>Generator 作为 AST 抽象语法树也是一个 Tree 结构。 所以业务逻辑只要领域化之后,也是可以定义有意义的差量修正。 Nop 平台中可以通过 task 或者 workflow 抽象来进行。 另外在平时实现代码生成器的时候我们会使用 xpl 标签库,标签库中的每个标签函数可以被单独定制。

目前我还在处理规则引擎。业务规则如果用 XML 来表达,可以是:

<rule>
<predicate>
<eq name="status" value="1"/>
<in name="type" value="${[1,2,3]}"/>
</predicate>

<outputs>
<output name="bizType" value="1"/>
</outputs>
</rule>

在系统采用差量机制后,那使用版本机制还有意义么?

Git 的版本相当于是一个比较粗粒度的非结构化差量, 可以给一次大的更新起一个名字,可以处理一般的 java 源码。 而 Delta 我们希望它是高度结构化、语义化的,如果所有的语言包括 Java 都实现合适的 Delta 差量化,则可以起到类似 Git 的作用。

据说 SAP 软件的更新包就是 Delta 化的,可以在不停机的情况下进行代码结构更新。 因为它所有的业务逻辑都是用自己的脚本语言实现的,而状态完全保存在数据库中, 脚本语言的加载器显然可以在加载的时候做很多替换工作。

那理想系统的整体工作量还很大,还需要实现一门 Delta 语言

Delta 只是一个特性,只需要在原语言的基础上加部分特性即可。 其实可以在 AspectJ 的基础上做简化就可以实现类-函数级别的 Delta。 C# 语言中的 PartiaICIass 也是一种弱化的 Delta。

对 Delta 的管理也需要一种基线之类的版本机制吧?或者单独对其使用 Git?

是的,可以用 Git 去管理 Delta。因为很多时候我们就想直接改 Delta 的实现,不需要保留语义边界。

xdef.xdef 中的 <meta:unknow-tag...> 为啥是代表任意名称的节点,不是应该就是指定名字是 unknow-tag 的节点吗?

xdef.xdefmeta 名字空间对应 xdef,而 xdef:unknown-tag 是平台解析时识别的节点。

<meta:unknown-tag> 在解析时是不会被识别解析的,但是为什么说是代表任意名称的节点呢?

unknown-tag 就是约定表示任意节点。

如果是这么约定的话,那 xdsl.xdef 文件,开头为啥非要用 xdef:unknow-tag 作为根节点,不是可以随便 abc

xdsl.xdef 表达的是所有 xdsl 领域语言所具有的共性。 wf/beans/sql-lib 这一系列的 xdef 所描述的 dsl 都是 xdsl, 除了它们各自具有的 xdef 元模型定义,它们还同时具有 xdsl.xdef 中描述的共性特征。所以 xdsl.xdef 中的节点匹配任意节点。

可不可以这么理解,xdef 文件是自定义出来的一种 dsl 语言,并不是严格意义上的 xml,所以不能按照 xml 的思维去理解。比如,api.xdef 里面,直接出现 xdef:xxxx 这样的属性,但是并没有严格声明 xdef 的名称空间。

是的。Nop 平台中的 xml 是使用 XNodeParser 解析的,并没有采用 Java 标准中的 xml 解析器。XNode 对于名字空间进行了简化设计,只考虑根节点上的名字空间,而且不校验内部节点的名字空间。 只有 xdef 的根节点上指定了 xdef:check-ns 的时候才检查这些指定名字空间的属性是否在 xdef 元模型中有定义。

那如果用 XNodeParser 去解析标准的 xml,遇上属性名称或者节点名称不在某个名称空间里面,也是校验不出来的是吧。

比如 a 命名空间下,只有 x 这个节点,然后 <a:y /> 是能过的。

Nop 平台按照可逆计算理论,它的设计永远是 data + metadata 配对设计, 也就是任何数据的临近之处总是设计有存放扩展信息的地方,因此 xdsl 语法中规定, 除非特殊指定需要校验名字空间,所有具有名字空间的属性和节点是不参与校验的。 这样就可以实现自由扩展,可以在不改变原先模型定义的情况下随时增加 Delta 模型定义。 如果需要检查 Delta 定义满足指定格式,可以自己写一个 xdef,使用 x:extends 从原有的 xdef 继承,在自己使用的 xdsl 文件中指定使用自己的 xdef 即可。 平台内置的解析器可以根据指定的新的 xdef 来解析 xml 文件,不需要执行代码生成。

使用 Nop 平台开发的所有功能,包括各类设计器、内部模型,都可以通过 Delta 定制进行扩展。 比如在平台内置的工作流设计器中增加配置属性或者删除已有的配置数据, 都可以通过在 Delta 目录下增加 Delta 定制文件来实现。

备注

也就是,ui:showi18n-en:displayName 等都属于扩展信息,且本质上也是 Delta,用于附加其他领域空间的配置信息。

那要解析并校验一个外来的 xml,人家本来就有标准 xml 那套 xsdl 名称空间定义,用 XNodeParser 去解析校验,还要自己重新造一遍轮子? 写一遍这个 xml 的模型定义 xdef 文件?

当然可能只要造一遍通用的轮子,按说的扩展机制,造一个解析 xsdl 转成 xdef 的扩展出来,再用生成的 xdef 去校验?

XNodeParser 并不会识别 xdef 名字空间,它只是把 xml 解析为 XNode。 一般的 xml 都可以解析,只是不会校验名字空间。DslModelLoader 之类的才会识别 x:schema,这种情况下,原则上 dsl 文件需要回避 xdslxdef 等内部名字空间, 但是也设计了冲突回避方式,就是使用 xmlns:xx="/nop/schema/xdef.xdef" 这种方式来重命名名字空间,比如 xdef.xdef 为了避免冲突将 xdef 重命名为了 xmeta。 但是这种重命名目前没有仔细检查,除了已经使用的少数场景,其他情况下有写死根据名字空间来处理的情况。

通过 Delta 可以管理产品主线和客户定制化的关系,与现在的 Git 版本管理不是一样的吗?

产品线难管是因为分支太多,膨胀了,没办法管了。

至于说的 Delta,可以认为 Git 产生的源码 diff,就是一种 Delta,只不过是跟目标环境语言绑死了而已。

不一样。Git 不是面向领域的,所以导致它的冲突概率大得多,多个分支之间的 Delta 定制方向也没有明确定义,导致意图丢失。比如一行中有两个属性修改,Git 就无法自动合并,而 Delta 定制永远都是可以自动合并的,而且通过 x:override 可以保持意图。本质上是信息空间中的表达和保持信息守恒的问题。

a 分支要 override 成 a,b 分支要 override 成 b,那还是冲突啊。

如果要按顺序叠加,base <- a <- b,这是线性应用的,多层应用按照执行顺序会自动解决冲突问题。 了解一下 trait 为什么可以避免面向对象中多重继承导致的概念冲突问题就明白了。

  1. 首先使用自定义结构空间而不是预设的通用行空间、类空间;
  2. DSL 相当于定义了一种领域特定的坐标系,在这个坐标系中可以精确定义定点的 Delta 变化。 可以类比物理中的狄拉克 Delta 函数去理解;

按顺序叠加这块明白了,冲突是没了,这样叠加完之后,得到的结果其实并不是用户最终要的,还得再叠加自己的东西上去。

本来就是这样。Delta_a + Delta_b 并不一定满足要求,这里可以通过增加额外的 Delta_c 来解决冲突问题。在 Feature Oriented Programming 的语境中 Batory 发明了一个所谓概念叫做 quark 来表达对这种冲突的识别和处理。

但是这种差异的识别也很难,顾客看到了一个产品的两个差异版本,他是不知道底层是多少个 Delta 组成的,他只会提出,需要这个版本里面的 xxxx,然后那个版本里面 yyy,合在一起就好了。

但是 xxxx,和 yyy 到底又有多少 Delta 组成的,这个其实最终跟分析源代码也没啥两样。

这样要求 Delta 满足交换律,本身如果的 Delta 不冲突,那么它们自然是满足交换律的, 否则的话 a + bb + a 是不同的,本身就需要额外再增加 Delta 来补齐。

其实产品线合并的痛点在这里。

产品线合并的痛点在于没法将产品化的部分和定制化的部分分离。可以分成 platform/product/deploy 这几个 Delta,然后对平台的修正和对产品的修正就可以独立于的部署修正隔离开来。 这些本身也需要一定的规划,不是随便乱来的。

其实怕的就是最后一层 deploy 的那个真的是五花八门,合起来必然有冲突。

可逆计算的观念是通过最外层的 Delta 来吸收偶然性需求,保护基础框架和 Base Delta 的稳定。 无法控制熵增也可以控制熵增的地方,这才是关键。 Delta 合并不会出现形式冲突,合并后必然满足 xdef 元模型要求,可以执行语义校验。

首先应用大了还是会分不同的业务模块,领域模型。 然后差量并不是用来组装不同业务功能点最终合成一个大产品, 而是对某一个业务模块的不同层次的差量定制。 最常用的应该就一个基础版本加一层差量,能做的扩展性就已经非常大了。

要是延续之前做法,确实不好。业务域也需要分割好。然后一步一步组装起来。

是的,差量不是用来进行分模块逻辑组织的。 就好像不要使用继承来进行分模块逻辑组织。 Delta 是用来解决可扩展性问题,解决含时演化的。

Delta 定制解决的问题是系统已经打包发布之后,无需修改原有代码,可以随意定制已有的所有功能。 所以平台的二次开发是无需修改平台代码的。 目前没有其他技术具有这个能力,它解决的问题也是传统技术所无法解决的问题。

Nop 平台目前就提供了粗粒度软件复用的机制,可以在不修改一个非常大的 X 的基础上,通过补充 Delta,将它转化成 Y。而所谓的可扩展性,本质上就是不修改 X,通过补充新的信息来达到生产新的功能的目的。

可逆计算主要涉及到的是编译期形式变换,运行时形式封装只是一个次要的问题。

DSL 最终要被生成成特定的语言,这样,Detla 定制就绕不开语言?

就好比现在的 Nop 平台是 Java 实现的,当需要做一个 Detla 叠加时, 好像没办法用 Python 或者其他语言就 override 或者修正某一部分 Delta。 尤其在外层修改原有功能时,必须还是要去考虑原有层的代码。

并不是这样。Nop 平台的做法相当于是标准化 Delta 空间。传统上的 Delta 都是针对特定业务领域、特定框架设计的,比如 Spring 中有 parentabstract 等属性,需要解析得到 BeanDefinition 对象然后处理,GraphQL 有 extendtype 等扩展定义,需要解析得到 GraphQLObjectDefinition 等对象后进行处理。 但是可逆计算理论是指出在对象层之前存在统一的、厚重的结构层, 恰如各式各样不同的建筑风格最终都使用统一的结构力学来构建。 所以本质上 Nop 平台中的做法是跨语言、跨框架的,它通过统一的 XNode 接口来规范化结构层,然后定义了 XNode 和 XML、JSON 等通用结构的双向映射关系,可以自动实现可逆变换,然后再逐步定义 JSON 和 Java 之间,JSON 和 Excel 之间,JSON 和数据库之间的双向映射。

关键的关键是无需考虑任何运行时框架的语义,在纯粹的 XNode 结构层面(形式层面)就可以完成所有可逆计算所要求的 Delta 运算。 无论是工作流引擎、报表引擎、ORM 引擎等都是统一使用同样的 XNode 结构。

其实就是全部面向 DSL 开发,Generator 就是虚拟机。

Nop 平台整体解决问题是两阶段方式:

  1. 定义领域专用的 DSL;
  2. 用 DSL 去承载业务;

DSL 可以在 XML/JSON/Excel/数据库/可视化设计器 等多种表示形式之间自由转换。 也可以通过 Generator 最终转换为某种程序代码。 但是这种生成是业务无关的,可以脱离业务一次性完成。

如果学习过物理学中的张量概念就会知道,理论的概念是唯一的, 但是它可以投射到不同的具体的坐标系中产生不同的表象,而不同表象之间可以自由转换。 所以 Nop 平台的做法很简单, 就是存在信息的某种类似张量的与具体坐标系无关的(与语言、框架、运行时无关)表示, 然后再定义它到具体的形式之间的表象(相当于是投射到某种坐标系中,一种语言就定义了一种坐标系)。

这些都是物理学中的概念,可以把上面的文章中关于 Talyer 级数的直觉解释仔细看一下。 努力的方向是,信息可以跨形式自由转换,而不是一旦写成代码就禁锢在某种框架、某种语言、某种运行时中。

物理学的定律是所谓坐标系无关的,这是爱因斯坦所提出的最重要的物理思想。 定律的张量形式不随坐标系变换而发生变化。但是在使用具体的坐标表达时,物理学的公式形式是在不断变化的, 但是一旦整理成张量形式,给张量指定一个抽象的符号,那么无论怎么变换这个符号都是不变的。

物理学中的张量与深度学习中的张量概念在内涵上是不一样的,不是同一个东西,但是有关联。

物理学中所有的基本概念都是坐标系无关的,但是所有具体问题的表述都要依赖坐标系, 那怎么能说物理定律与坐标系无关呢?这里的无关真正的含义是同样的物理定律在不同的坐标系中具有不同的形式, 恰如同样的业务在不同的框架、不同的语言中表达式也具有不同的形式,但是这些不同的形式背后存在统一的抽象内容, 可以使用张量概念进行统一描述,然后把它投射到不同的坐标系中就自动产生了不同的具体表达。 可逆计算所实现的是业务的技术中立的表达,然后通过一系列的可逆转换实现它向不同坐标系的投射。 当然这种技术中立的表达本身也是需要一个坐标系的,也就是内置的 XLang 语言(包括 xlib,xpl,xdef,xdsl 等), 但是 xdsl 相当于是物理学中的内禀坐标系,一种在领域内部具有某种最优性的坐标系。 这种最优性可以通过需求变动时如何变化最少量的表达量来精确的度量,在信息论的角度上说可以进行某种精确的定义。

现代数学与古典数学的一个本质性区别在于,现代数学是建立在等价类概念基础之上的, 当们说到任何一个数学对象时,指的都不是某个特定的数学对象, 而是在某种等价规则定义下的一系列相互等价的具体对象所构成的等价类。

我们习以为常的 1,2,3 这种数学上最常见的基础对象,在严格的数学论述中也是每个数字都是由无穷多的对象所组成的等价类。

彭罗斯在《通向实在之路》一书中介绍了一个有趣的事情。他母亲的一位朋友(芭蕾舞演员)完全不能理解分数: 为什么 1/2 = 2/4,这两边明明差别很大,为什么会是一样的呢? 彭罗斯经过仔细思考发现了其中的华点:虽然他母亲的这位朋友可以说对数学一无所知, 但却通过敏锐的观察力洞察了现代数学的秘密。分数的现代定义是将 1/2, 2/4, 3/6, .... 这样的无穷序列看作是一个等价类,然后在这个等价类中选择形式最简单的 1/2 作为它的代表元, 每当们使用这个等价类的时候,们都可以用这个代表元来表示。

《通向实在之路》是一本很有趣的科普著作。如果读懂了这本书,可逆计算中涉及到的一些简单的物理数学观念就是不言自明的了。

「原子变换的复合」是什么意思?

说的是各种变换是可以复合的,并不需要一个个手工编制。 比如说可逆性是可以复合的,如果我们在字段级别建立了  文本表示 <=>  可视化展现  这种可逆变换,那么整个体系应该提供一种自然的复合方式得到一个复杂结构的   文本表示  <=>  可视化展现  这种结构,而不需要再用手工去从头开始实现。

这主要是为了由 AI 来自动找到 Delta 是么?

不是,这只是利用自然界存在的规律性而已。有人问过一个问题: 类型表达式是不是一个约束函数? 我的解释是,类型确实也是一种约束,它相当于是把运行时的一些限制投影到类型空间这样一种同态映射。 其实规律性本身就是一种约束性。但我们可以不从约束的角度去理解。 广义相对论中引力不再是一种被动的约束,而是空间弯曲后处于完全自由状态不受力的情况下自然的运动。 只是在领域空间中自由移动而己。这种移动可以确保下一个位置还在领域空间中。 对于这种情况,我们可以说费了很大的劲才确保不会破坏领域空间,最终仍然保留在了领域空间中, 也可以认为道法自然,随心所欲而不逾矩,我们很自然的就不会走到领域空间之外。 一言一行都满足领域规则,总是处于领域空间中而不自知。我们很多时候编程是为了避免偏离计划的情况出现, 好像处处都会出现问题,但是为什么不顺应规律,选择最自然的表达方式呢?

我举个例子。Nop 平台的后台是定义 BizObject,然后定义 BizObject 上的方法, 然后自动推导得到前台 rest 链接是  /r/{bizObjName}__{bizMethod}。 我们当然可以为每个对象指定不同的 rest 映射方式, 但是这样自动推导得到的标准映射方式压根就不需要编写代码,不是更好吗? 而且它保证了数学层面上的一种同态映射关系。在 GraphQL 层面它对应于 query { NopAuthUser__findPage} 这种方法,而在 grpc 层面, 它对应于 graphql.NopAuthUser/findPage,很自然的, 在多种调用协议层面,我们可以自动建立一种可逆的结构转换关系。 所以 Nop 平台中编写的服务函数,可以无需任何适配修改,就自动发布为 REST 服务、GraphQL 服务、Grpc 服务, 可以自由的在多种形式之间转换。这种转换的可行性,来源在于在信息表达层面本来就是等价的, 这种形式上的可转换性只是底层信息一致性的自然显露而已。但是有多少时候, 我们自己随意的一个链接命名就破坏了这种数学层面的自动等价性, 不得不依靠程序员手工编写适配代码来弥补其中的信息缺失。 而这种不等价性并不是业务结构内置的,只是由人的随意性所引入的。

为什么在后台我们要定义 BizObject,然后定义 bizMethod。 因为在数学层面上,我们总是要对后台的信息结构进行分解的,而如果我们进行了最粗粒度的一级分解, 给分解后的组分起一个名字,它自然的就成为对象名,然后再进行二级分解,就得到方法名。 整个 Nop 平台的设计可以从数学层面,按照最小化信息分解的规律逐步推导出来。

Nop 平台的设计是可以在数学层面上进行解释的,因为数学规律的普遍性, 所以它的思想和很多其他领域中的做法都可以进行比较。

大部分人做设计都是拍脑袋,导致对于设计的必要性是语焉不详的。 比如说比较 GraphQL 和 REST,到底孰优孰劣?有些人说各有应用场景,有些人说如果必要勿增实体, 有些人说 GraphQL 解决了 XX 问题,远远超越 REST,谁也说服不了谁。 但是从数学层面分析,GraphQL 经过形式变换后可以等价于 REST + SelectionSet, 它在数学上严格优于 REST。基于这样的认识,反向的可以认识到现有的 GraphQL 引擎实现存在问题,需要进行相应的改造。

Nop 平台并不是简单的使用数学和物理学中的概念进行类比, 就好像我们并不认为关系数据库是使用关系模型的数学理论进行类比一样。 它是严格按照数学结构进行推演并且落实到代码设计中的。 本身可逆计算的核心公式 App = Delta x-extends Generator<DSL> 是在数学层面可以进行严格定义的运算结构,其中 DSL 的 tree 结构和 x-extends 算子都具有明确的数学定义,并且可以证明 Delta 差量运算满足结合律。

一个角色对于某个字段的权限可以运行时修改吗?

可以。xmeta 中可以指定 permission,然后在线配置 role 和 permission 的关联。

这个是通过“系统表”吗?

不是。有个 NopAuthResource 表定义功能点对应的 permission,NopAuthResourceRole 表配置功能点和角色之间的关联。 在 xmeta 中可以定义字段对应的 permission。

这地方能直接对接上 casbin 之类的就好了

Nop 平台的权限设计是基于数学推理的最小化权限模型。要控制   user -- action/field 之间的许可关系,如果具有 M 个 user,N 个 action, 直接控制就是指定一个 M * N 的矩阵。如果我们要简化配置, 在数学层面就是引入矩阵分解 M * N --> M * P * Q * N, 引入两个中间矩阵,且这两个矩阵的维度都远小于 M 和 N, 则我们需要指定的配置项个数就是 M * P + P * Q + Q * N, 大大小于原始的 M * N。具体的做法就是 user -- role -- resource -- permission。 所以 casbin 之类的模型如果支持数学层面的最小化设计, 那它一定可以和 Nop 的权限模型进行适配的。

所以这个 P Q 如何选择,又是需要经验的事情?但是可以随意调整?

是否可以随意调整依赖于领域本身。在数学上确定一个结构之后,落实到领域上我们会把它对应于领域概念, 赋予某种业务含义。所以 P 对应于 role,而 Q 对应于 resource 之类的。 确定业务含义之后在领域内就一般存在着自然的粒度控制。可以这么说,我们想在结构中插入两个中间结构 PQ, 然后对比一下我们的业务知识,发现它们已经存在于业务人员的头脑中,然后可以很自然的做出选择。 实际上一个有用的结构不是因为技术层面它有用,而是因为它反映了领域内的某种规律性知识。 而一个领域如果发展一段时间相对比较成熟以后,有用的概念本身一定会有领域相关的名词对应, 并且它的粒度存在一种隐性的知识。但是,从数学层面理解之后,可以让我们更精确、更坚定的控制相关概念的应用边界, 并在多种可行的选择中选择相对成本最小的一个。

2024-02-17 要点整理

dsl 不是为了扩展点,扩展点只是一个副产品。百度 amis 的形式可以实现所有需求, 并不是百度 amis 现有的组件可以实现所有的需求。本质上 Nop 平台处理的只是形式问题, 其实所有框架如果都按照可逆计算的思想改造,那么它们可以很自然的在多种 dsl 形式之间转换。

Nop 是在更高的抽象层次上解决问题,不是使用某个特定的运行时来解决特定的需求问题。 和传统上的框架技术有着本质区别。

jsx 的灵活性导致它是无法被逆向分析的。所有可以逆向分析的 jsx 本质上都是只使用某一个 dsl 子集。

Nop 平台强调的是信息的自由流动,而不是一旦写成代码就只有程序员可以对它反向分析。

所谓的软件是为了满足客户需求,客户需求本质上是技术无关的。 而目前我们所有的写代码行为都导致客户需求与特定的技术实现绑定了,这个并不是必须的。

如何让信息可以跨系统、跨软件自由的流动?第二次工业革命是电气化,能量在多种形式之间自由转换, 都可以转换为电能。而现在信息是绑定在某种形式中,它应该需要有更灵活的流动方式。

只是要促进信息的自由流动,提供它们可以流动的结构构造规律而已。

Nop 平台所提供的不是一种低代码产品,它只是可以去做低代码产品而已。本质上是一种新的软件构造理论。 你总是从太具体的东西去理解实际上是不合适的。首先,它是一个理论。 然后这个理论带来一些新的做法,这些新的做法导致此前无解的一些技术难题可以被解决。

如果信息完备是可以在各种形式间自由转换的。但是目前的软件偏向于手工编写, 所以很多层面的信息表述不完备,从可逆计算角度的去分析就会发现这些技术存在扩展性上的问题。

每一个足够复杂的程序框架本质上都是在提供一种潜在的模型,但是很多模型没有明确的 DSL 文本表示形式,导致无法被其他工具很容易的进行分析、进行信息转换、进一步的处理等。 而 Nop 平台强调 DSL 优先,所有模型都先定义清楚自己是什么, 类似于你要写数学证明至少要先讲清楚自己所使用的概念到底是哪几个。 然后 Nop 平台会自动根据 DSL 推导得到它的可视化展现形式,自动生成可视化设计器。 而传统上你自己有一个模型,所有的 IDE 工具、可视化设计器都是要自己去写的, 还不能保证与自己的模型同步更新。

Nop 的核心内容可以看作是一个领域语言工作台,它提供理论基础、技术工具、运行时引擎等综合手段, 极大的降低开发自定义 DSL 的成本,使得每个人都可能快速开发自己的 DSL, 并自动得到 IDE 提示、可视化设计器等外围工具。我们只需要专注于元模型也就是核心信息结构的定义即可, 然后所有从数学层面可以推导得到的信息都可以自动得到。

组件技术在理论层面就无法实现系统级别的复用,必须引入差量概念才可以改变软件复用的基本原理, 从复用方向向下的整体-部分复用机制转向复用方向平级甚至向上的转化复用。  从相同才可以复用扩展到相关或者相似就可以复用。这是从基本的软件复用原理方面的变革。

可逆计算的核心结构空间一定是 DSL,也就是自定义的结构空间,而不是某种通用的程序语言结构空间。 试图提供一种通用程序语言,它只会是可逆计算的一个非常局限的应用,而不是可逆计算理论所指向的核心内容。

  • 一种语言就是一个坐标系统,用语言去表达业务相当于是用坐标系的描述去表达几何对象
  • 微分流形理论(大概是 18 世纪高斯和黎曼时代的知识)认识到无法将局部的欧式坐标系推广到几何体整体, 必须使用多个局部坐标系,然后把它们拼接在一起。Nop 平台提供了创建新的 DSL 和粘结 DSL 的工具, 相当于是使用多个局部坐标系来描述业务
  • 在坐标系统上引入更加灵活的差量运算,使得结构构造规律更加丰富化

从可逆计算理论可以非常明显的看出,现在很多做低代码的厂商试图建立一个通用的 DSL 来覆盖所有的业务,这在本质上是徒劳的。因为复杂对象的降维描述本质上需要的是多个坐标系统。

  • 我们需要使用领域特定的坐标系,所以需要多个 DSL
  • 在结构层面,所有的 DSL 都可以采用 Tree 结构来表达
  • Nop 平台在结构层面相当于是提供了一组通用的 Tree 结构的生成、解析、转换工具。 而传统的程序语言在结构层面表现为类-属性这种短程结构,相当于是 Map

应该说绝大多数人没有想到 delta 可以表达减法和逆变换的思想。在学术界就很少有这种想法, 以至于提出 Delta Oriented Programming 的这个人在德国靠 DOP 评上了教授。 另外,没有人想到 Delta 可以是非常通用的概念,大量的做法都是针对某种结构定义 delta, 没有认识到 Delta 这个概念的抽象性和普遍性。

认识到 Delta 的少数人也没有认识到 Y = F(X) + Delta 才是完整的表述, 可逆性实际上在这个公式中实际上体现在两个方面,一是 Delta 差量合并,二是 F 可以是某种可逆变换。 在 Nop 平台中是系统化的应用数学上的同构和同态变换的思想, 尽量通过原子变换的复合来构造更复杂的结构变换。

信息空间中最有效的生产方式不是组装,而是掌握和制定运算规则

为了克服组件理论的局限,我们需要重新认识软件的抽象本质。 软件是在抽象的逻辑世界中存在的一种信息产品,信息并不是物质。 抽象世界的构造和生产规律与物质世界是有着本质不同的。 物质产品的生产总是有成本的,而复制软件的边际成本却可以是 0。 将桌子从房间中移走在物质世界中必须要经过门或窗, 但在抽象的信息空间中却只需要将桌子的坐标从 x 改为 -x 而已。 抽象元素之间的运算关系并不受众多物理约束的限制, 因此信息空间中最有效的生产方式不是组装,而是掌握和制定运算规则

Nop 与其他工具之间最根本的差异是 —— Nop 背后的原理是「客观规律」, 是根据已有数学与科学研究「推导」出来;而其他工具是根据开发活动经验的累积「总结」出来的。 这两者之间在逻辑层面的严密性差距不言而喻。

注解或者超链接这种形式其实老早就有了,都知道 word 其实是 xml

你没有能理解这里真正重要的数学层面的构造,只关注到一些表观的细节, 以为使用了注解或者超链接这种形式就是设计的全部。 你应该仔细读一下文章,努力去尝试理解与自己思维不同的部分, 而不是在文章中只看到自己曾经熟悉的名词片段,这样的话你永远理解不了新的设计。

在 NopReport 的设计中,关键的部分是 Template = 原始模型 + 扩展配置。 也就是说,任意的原始模型都是一个合法的模板,我们是在原始信息之外再补充扩展配置即可。

Template = BaseModel + ExtModel
TemplateEditor = BaseModelEditor + ExtModeEditor

更进一步的推理是,我们可以在保持原信息的可视化设计的同时, 补充一个扩展模型的可视化配置,就可以实现报表模板的配置。 利用这种方案,我们可以实现任何模型的模板化扩展,而且只需要补充 Delta 信息。

如果 Office 内置了某种扩展机制,比如说 Office2003 的自定义标签机制, 那么可以直接使用,根本不需要用到超链接。 正是因为 Office 的设计没有按照可逆计算理论进行,才使得我们无法直接将它作为模板编辑器, 需要做一些额外的形式转换工作。

你只看到了 Office 使用了 XML,XML 比 POI 好处理,但是没有理解为什么 XML 比 POI 好处理。 XML 是通用的结构层面的表达,而 POI 对象是有着不同的数据类型的对象层面的表达。 可逆计算强调我们在将信息转化为对象之前,存在统一的结构表达层可以直接在这个层面完成很多通用操作没有必要把处理放到对象层。 对象层每个对象的类型不同,处理规则是不同的。类似于不同的建筑具有同样的工程力学, 在结构层看来,很多业务层面不同的东西本质上是一样的。 不使用 XML 表达,使用 JSON 或者任何一种通用结构化形式来表达也是一样的。 而且 Nop 平台的设计不需要做任何改变,只需要插入一个形式转换适配器即可。

你也没有理解 XPL 模板语言在其中的作用。为什么有了通用的结构层表达之后,立刻就可以得到它的可执行态。 XPL 在数学上的定位是什么?它将任意的 AST 表达树通过语法制导翻译转换为可执行逻辑。

这是一种系统化的解决方案,并不是仅仅用于 Excel 和 Word 生成的一种聪明的技巧。 你只有理解它在数学上对应于什么样的通用变换,才能理解它为什么这么简单却有用。

另外,选择超链接是因为目前在 Word 中它的可视化形式比较直观,而且插入后位置定位比较准确。 其他方式,比如插入域等在结构层往往会错位。

网易 codewave 的设计怎么样?

codewave 的 DSL 设计不行

主要是在哪,我看他们是实现了一套 NASL 的东西

从可逆计算的角度分析,NASL 处处都是问题:

  • 没有统一的元模型
  • 应该是多个 DSL,DSL 应该是允许自定义扩展的,而不是固化死板的一个
  • 没有差量定义
  • 任何一个具有足够内在丰富性的语言都应该提供分解、合并、二次抽象的手段,NASL 没有
  • 缺少自动推理得到可视化界面、IDE 设计工具这些外围支持

拍脑袋设计语言,靠经验积累属性。

DSL 本身是可编程的,即便有一个固有的规则应该可扩展性也很高吧,像是苹果刚开源的 pkl。

不是。可编程不意味着可扩展。你比较一下 nop 平台中的 dsl 和 xpl 模板语言就知道了。

基于可逆计算,有领域坐标系,可以进行可逆变换的复合,就自动支持可视化了。

不需要特殊语言设计,本质上只是信息结构的设计

形式不重要。在数学中一切形式都是虚妄

我理解是不是这个样子。像是普通的 DSL 描述主要是做到:

  • 元数据的描述
  • 数据本身的表达
    • 以上两者是一对一,不同定义之间是隔离的,部分 DSL 达到了
  • 元数据以及数据本身表达的模式是可编程的,动态的

NOP 在顶层又抽象了一层做到了:

  • 数据本身的表达是可以互相抽象组合的,即便数据的结构基于不同的元数据定义
  • 元数据定义也是作为一种数据存在,本身也有自己的元数据

所以 NOP 里的 DSL 分解组装、组装,本质就是数据的结构变换,DSL 的定义可以最小化表达。

大部分 DSL 没有明确的元模型。定义自己的 DSL 的时候应该加一个 schema 属性,指向自己的元模型。 DSL 的本质性价值是对领域内逻辑的高密度表达,这个具体抽象的是否合适要看是否满足领域需求, Nop 平台的 DSL 也只是一些示例。通过对比,可以发现其他类似的 DSL 设计存在哪些问题。

可逆计算指出的是结构构造具有统一的规律,所有 DSL 可以共享这些规律,这里就是一些纯形式层面的做法, 与业务领域无关了。但是这些规律使得我们开发 DSL 的时候可以更加灵活、简单, 这里涉及到 DSL 的无缝嵌入,动态转换、自动推理等,这些能力在一般的 DSL 设计里是没有的。

一般的 DSL 设计就相当于一个工匠做了一把锤子来解决某几个钉子的问题,但是要搞懂牛顿力学, 你才能普遍性的解决所有力学相关的问题。

如果把所有 DSL 所构成的空间看作是一个物理世界,我们要问这个世界中的牛顿力学是什么?

nop 的扩展性很灵活颗粒度可以很小,那么当在这个之上做扩展,怎么避免不恰当配置导致功能受影响?

比如把某个源的 bean 关了就影响了功能

Nop 只解决形式空间的变换问题。最终语义空间的问题需要自己想办法保证,自己增加约束校验。 框架层面在各类引擎会进行一部分的语义检查:

  • xdef 元模型中会定义格式要求,模型合并完之后会经过 validator 校验
  • IoC 容器在 mvn install 打包以及系统启动的时候会自动检查 bean 的依赖关系
  • ORM 启动的时候会检查所有的表引用和属性引用正确
  • XView 模型会检查所有引用的字段如果没有标记为 custom,则必须在 XMeta 中定义

总之,在各类引擎中,可以根据自己的语义增加一些检查,类似于增强型的类型检查。 xdef 就是比程序语言类型更强的一种检查。但是各个引擎内部可以引入业务语义相关的更强的事前约束。

可逆计算理论中,Delta 合并发生在结构层,此时各种运行时非法的结构都在可以存在的, 因此这里是可行空间,是所有可能出现的结构的最大集合。类似于量子力学在观测之外是可以打破能量守恒定律的, 各种量子隧穿是允许的。但是合并完成之后,真正进入可观测、可运行的世界,这时需要经过一系列的规律约束。 Nop 平台内置了多阶段编译能力,在真正运行前提供了很多图灵完备、应用层可介入的验证时机, 甚至可以类似于 contract based programming,插入单元测试,在调试模式下通过某些样例测试才能启动。

传统的编程中,结构空间是封闭的,它仅由编译器厂商定义。Nop 平台相当于开放了结构空间, 允许自定义的结构规则,将编译器的能力开放给应用层。前端领域,随着 babel 的流行, 一定程度上已经开放了编译器的部分能力,但是除了少数框架,真正在应用层能用上 babel 的能力的很少。 Nop 提出一系列的结构规则和使用模式,使得这种能力的使用变得更加简单、直观。

概念层面很简单,就是如果把软件的结构空间看作是一个世界,那么就要问, 这个世界的牛顿力学和微积分是什么?它的基本的结构构造规律是什么?

结构空间是不是就是指 AST,AST 的特性就是牛顿力学,微积分就是语法制导?

或者可以理解为,AST 的每一个节点上通过标签加算子,这样的一个算子就是 delta?

语义就是这个世界的运行规则?

在可逆计算理论中,结构空间就是一切用 Tree 结构表达可及的空间,其中包含 AST, 还有传统上我们不认为是 AST,但是概念层面等价于 AST 的 Tree 结构,比如文件系统树。 在这个结构空间中的微积分,第一步肯定是需要建立差量的概念,然后再定义差量如何合并, 如何根据差量产生差量等。语义实际上是对结构的解释。数学层面上一切都是形式, 各种不同的东西都可以对应于同一个形式。但是在特定的应用中,我们会把同样的数学结构解释为不同的物理意义。

在现代物理学中,牛顿力学实际上可以看作是一种最简单的因果关联手段。 假设结果和原因之间存在线性关系,力学中的结果就是加速度,然后我们把它的原因定义为力, 实际上不是先有力,再有加速度,而是我们观察到加速度,然后反向查找,假定存在一个原因叫做力。 结果 = 系数 * 原因。所以在广义相对论中,最终力的概念被几何空间的自然趋势所代替, 之所以这样运动,不是因为受到了力,而是因为空间本来就是这样弯曲的。

如果拿序列图来讲,一个序列图代表一个业务流程,通过表达形成 Tree, 这个 Tree 是执行 Tree,操作的是业务数据,最终达到业务目标。

差量最初级是 java 中的一个语句,语句的合并形成一个固化的业务操作, 差量要想用好本质是要从数学的意义上去分解具有正交变化的业务操作最小单位, 然后通过 Tree 组合在一起,形成特定的业务。

如果找不到也没有关系,通过量的积累从中去找到最小的变化。

如果相对于现有的软件工程而言,差量最重要的概念是它是独立存在的,可以独立于本体存在, 并且差量可以复合产生更大结构的差量,最终全量也是差量的一个特例。 具有一定结构的差量一定是正和负的混合体。从局部变化的角度去考虑系统的构造, 相当于把系统放到时间轴上观察它的演化,然后从局部的变化如何累计成为更大粒度的变化, 但是这种变化仍然可以从本体上被剥离下来独立的研究。

观察的重点不是这个本体是什么,而是它的变化是什么。其实世界的本体是不可观测的, 我们观测的所有知识都是关于变化的知识。本体再复杂,它也只会成为一个背景, 并不影响到我们对于当前业务的处理。类似于操作系统再复杂,我们关心的业务也只是用到少数操作系统的 API 接口, 我们关注的重点是我们的业务如何实现,虽然它必然要运行在一个很复杂的操作系统之上, 但这只是背景信息。我们是为业务应用买单,而不是为它运行在其上的操作系统买单。 Docker 提供了一种切片机制,可以将应用切片在某些意义上从操作系统上剥离下来,然后独立的存放、管理、分发。

我们理解的单元是应用系统本身,而不是 应用系统 + 操作系统 这个整体。

经济学中的“边际革命”指的是 19 世纪末至 20 世纪初的一场理论变革,它主要改变了经济学分析的方法和重点。 这场革命的核心在于对“边际效用”(marginal utility)和“边际生产力”(marginal productivity)的概念进行了深入研究, 并强调了它们在解释经济行为和市场均衡中的重要性。

边际效用是指消费者在消费过程中,每增加消费一单位商品所增加的额外满足或效用。

经济学家如卡尔·门格尔(Carl Menger)、威廉·斯坦利·杰文斯(William Stanley Jevons)和利昂·瓦尔拉斯(Leon Walras)等人认为, 边际效用是决定商品价格的关键因素。他们主张,商品的价格是由消费者对最后一单位商品的愿付价格决定的,即边际效用决定的。

另一方面,边际生产力革命则是由阿尔弗雷德·马歇尔(Alfred Marshall)等人发展起来的, 他们认为生产要素的价格是由它们的边际生产力决定的。也就是说,一个要素的工资或租金取决于它对生产额外单位产品的能力。 边际革命对经济学的影响深远,它使得经济学从关注总量转移到了关注边际量,从而更加精确地描述了市场经济中的个体行为和决策过程。 这场革命也为后来的微观经济学理论发展奠定了坚实的基础,并影响了后来的经济分析方法。

差量化对我们最有用的应该是:

  • 代码的复用,可以支持最小化单元
  • 方便拓展,支持个性化
  • 在线设计可以丝滑转人工,不需要额外的工作

这是最初级的理解,可逆计算理论所指向的远不止这些。

为啥改代码的地方可以跟坐标对应起来?

可逆计算相关的技术文章对阅读者的要求有点高。 单单一个修改点的坐标化,很多人都无法理解,为啥改代码的地方可以跟坐标对应起来。

大部分人压根没有接触过三维以上的坐标系,更不知道高维空间和非欧子流形的概念, 所以坐标系压根就是一个无内容的概念。

在说到协程时,我曾指出结构化编程自然引入了一个坐标系统,而协程的实现本质上是记录执行空间的坐标, 然后暂停,最后再跳转到指定坐标处。就有人就反对说不需要坐标的概念,协程只需要记录上下文就可以。

但是这里的上下文是一个笼统的概念,整个内存都是上下文,但是坐标系是一个系统化的概念, 只需要少量坐标就足以描述整个运行时空间,而且它保证是唯一的、无遗漏的, 但是很多人在概念层面是无法分辨这些概念之间的区别的。

涉及到空间,就得有坐标系。

坐标系就是用来在某个空间里进行定位的,哪行哪个字符也是一种坐标系。