数据的本质
盘古 OS
的设计仍在逐步改进与完善中,本文将随时发生变化,感兴趣的朋友们可以时刻保持关注;- 在下方的版权声明中有本文作者的联系方式,有不同意见和相关建议的朋友可以与其保持联系;
- 也可在本文底部的评论区登录 Github 后,直接发表您的观点;
- 文章作者: flytreeleft - flytreeleft@crazydan.org
- 文章链接: https://studio.crazydan.org/docs/pangu-os/system-design/the-essence-of-data
- 版权声明: 本文章采用许可协议《署名 4.0 国际 (CC BY 4.0)》。 转载或商用请注明来自 Crazydan Studio!
数据结构
数据的结构包含属性和数据类型。
属性用于指示数据名称,并作为数据的引用标识以便于对属性做数据赋值和数据访问操作。 而数据类型则用于控制可对属性赋值的数据的有效范围, 比如,类型为整型数值的属性,只接受整型数值的数据。
除了数据类型以外,
属性值还涉及数据值可选范围(如,0-100
)、数据长度、数据不为空等对赋值的校验逻辑。
如果从数据赋值有效性控制上来看,数据类型其实也是一种数据校验,只不过是声明期便确定的,
而不是运行其动态验证的。
假如将属性视为赋值和取值函数,那么,赋值过程便是对参数有效性进行检测的过程, 有效的数据将最终与对象进行绑定。
function MyClass() {}
const dataStore = {
_x_value_: "xxx"
};
Object.defineProperty(MyClass.prototype, "x", {
get() {
// 从数据与对象的绑定中取对应属性的值
return dataStore._x_value_;
},
set(x) {
// 在对 x 校验后(必填、类型、长度等)再与对象进行绑定
dataStore._x_value_ = x;
},
});
也就是属性赋值的本质为带参数的属性函数调用,如,obj.name("Lily")
;
属性取值的本质为无参的属性函数调用,如,obj.name()
。
前者在对参数有效性进行校验后,将数据与对象属性绑定(存储数据),
后者则是取出与对象绑定的数据(查询数据)。
还有一部分只读属性,其值通过绑定对象(也可能是其他关联对象)的其他属性值计算得到,
比如,年龄 = 当前时间 - 出生时间 / 365
,其值是动态的,且不需要单独存储。
所以,结构化的数据,其本质为属性函数的集合, 不同数据的结构差异仅在于属性函数集合大小和函数内部对参数的校验逻辑的不同。
属性即类型,类型即函数
type alias User =
{ name: User.Name
, gender: User.Gender
, age: User.Age
, password: User.Password
}
type alias User.Name =
{ first: User.Name.First
, family: User.Name.Family
}
type alias User.Name.First =
Capitalize String
type alias User.Name.Family =
Capitalize String
type User.Gender
= Male
| Female
type alias User.Age =
(Between 1 150) Int
type alias User.Password =
Encrypt.MD5
type Encrypt =
MD5 String
user =
User
{ name:
User.Name
{ first: User.Name.Fisrt '三'
, family: User.Name.Family '张'
}
, gender: User.Gener.Male
, age: User.Age 28
, password: User.Password 'abcd'
}
user.name.first
-- String '三'
user.age
-- Int 28
user.password
-- String 'f5ac8127b3b6b85cdc13f237c6005d80'
user =
{ user | age = User.Age 200 }
user.age
-- Invalid (Int 200) 'User.Age 的有效值只能在 1~150 之间,但其实际值为 200'
- 结构数据的属性名称即为该属性的数据类型
- 通过 Namespace 限定类型所属的类型空间,避免名字冲突
- 实际使用类型 AST 树的 Hash 值作为引用标识, 在更改名称时,无需更改关联位置代码
- 数据类型也是函数,其将参数处理后返回有效数据
- 类型函数可嵌套以构成组合类型,并通过
alias
关键字对其进行命名 - 组合类型可由值转换、值校验等函数组合而成,从而实现对输入值的变换和有效性检查,
如:
(Between 1 150) Int
表示输入只能为1~150
之间的整数(Int
),Capitalize String
则表示对输入的字符串(String
)做首字母大写转换 - 仅在访问属性时才返回类型函数的处理结果(如,
String 'xxx'
,即,基础类型 类型值
形式)
标记语言
标记语言的目的是通过不同的标记符号指示对文本做不同的展示处理。 其作用本质上与函数是相同的,也即,标记符号就是函数名, 标记符号范围内的文本就是函数的处理参数, 在标记符上设置的参数,就是函数的配置参数。
比如,如下一段 Markdown 标记内容:
**这是一段粗体文本。**
`这是一段代码块。`
这段文本包含_斜体_。
用 Elm 函数实现就等价于:
document
[] -- Document 配置参数列表
[ paragraph
[] -- Paragraph 配置参数列表
[ boldText
[] -- BoldText 配置参数列表
[ text "这是一段粗体文本。"
]
]
, paragraph
[]
[ codeBlock
[]
[ text "这是一段代码块。"
]
]
, paragraph
[]
[ text "这段文本包含"
, italicText
[]
[ text "斜体"
]
, text "。"
]
]
从函数的角度来思考和实现各类标记语言,便可实现对其的统一, 可以以一套机制完成对各类标记语言的解析和文本渲染, 同时还可以按需重写其标记函数, 从而满足不同场景和需求下的文本处理和展示需求。
数据类型
-- 块
type Block =
Document Document
| Code Code
| Picture Picture
| Table Table
| Graph Graph
-- 文档
type Document =
Section Sentence
| Paragraph (List Sentence)
-- 句子
type Sentence =
List Text
-- 文本
type Text =
-- 字
Char Char
-- 词
| Word (List Char)
type Char =
-- 字符
Glyph String
-- 空白:含空白数量
| Blank Int