值
值
值是通过表达式计算生成的数据。值都有类型,每种类型都有与之对应的语法、运算符(操作符)和内部关联的类型。
名称 | 类型 | 直面值 |
---|---|---|
null | null | null |
逻辑 | logical | true、false |
数字 | number | 1.2、1e5、-.5 |
时间 | time | #time(09,15,23) |
日期 | date | #date(2022, 03, 05) |
日期时间 | datetime | #datetime(2022, 03, 05, 09, 15) |
带时区的日期时间 | datetimezone | #datetimezone(2022, 03, 05, 09, 15, 23, 08, 00) |
持续时间 | duration | #duration(1, 2, 15, 55) |
文本 | text | "hello" |
二进制 | binary | #binary("AQID")、#binary({0x65, 0x66}) |
列表 | list | {1, 2, 3} |
记录 | record | [A = 1, B = 2] |
表 | table | #table({"A", "B"}, { {1, "X"}, {3, "Y"} }) |
函数 | function | (x) => x + 1 |
类型 | type | type number、type [A = number, B = text] |
需要注意的是:并非所有的类型都有与之对应的值,比如:any
和none
。
null
null
表示缺失、状态不明确或未知的值。
null
是字面值,可以直接输入。
支持运算:
- 比较运算,结果始终为
null
- 四则运算,结果始终为
null
??
??
运算符比较特殊,它的作用是值为null
时赋予一个替换值。可用于任何类型,而不仅仅是null
。
x ?? y
// 等效语法
if x is null then y else x
null
值的原生类型是内部类型null
。
逻辑
逻辑值用于布尔计算,只有两个值:true
和false
。
逻辑值是字面值,可以直接输入。
M严格区分类型,逻辑值不可以直接与数字进行数学运算。
支持运算:
- 比较运算
- 逻辑计算
??
逻辑值的原生类型是内部类型logical
。
数字
数值(number value)用于数字和算术运算。
数字是字面值,可以直接输入。
.5
.5e+6
2.3E-3
0xFFFF
0xffff
数字至少以双精度表示(可以保留更高的精度)。双精度表示形式与[IEEE 754-2008]中定义的二进制浮点运算的IEEE 64位双精度标准一致。双精度浮点数的动态范围约为5.0e324
至1.7e308
,精度15-16位。
特殊值:
- 正零和负零。通常与
0
用法相同,仅在某些特殊情况下不同。 - 正无穷(
#infinity
)和负无穷(-#infinity
)。由非0
除以0
得到的。 - 非数值(Not-a-Number value)(
#nan
),通常写为NaN。由无效的数学运算产生,如:0 / 0
。
二进制数学运算使用Precision
执行。精度决定了操作数被舍入的域和操作的域。再没有明确指明精度的情况下,使用双精度执行此类操作(这里双精度指Precision.Double
)。
- 如果数学运算的结果对于目标格式来说太小,则运算结果是正零或负零。
- 如果数学运算的结果对于目标格式来说太大,则运算结果是正无穷或负无穷。
- 如果数学运算无效,则运算结果为NaN。
- 如果浮点运算至少一个操作数是NaN,则结果是NaN。
支持运算:
- 四则运算
- 比较运算
??
- 前置
+
或-
数值的原生类型是内部类型number
。
时间
时间值存储一天中时间的不透明表示形式。
时间值表示范围是00:00:00
到24:00:00
。精度为100ns
。
时间值可以使用内部函数#time
构建。
#time(hour, minute, second)
参数取值范围:
- 0 ≤
hour
≤ 24 - 0 ≤
minute
≤ 59 - 0 ≤
second
≤ 59
second
可以是小数,其他都是整数。
如果hour
为24,其余两个参数必须是0。
支持运算:
- 比较运算
??
- 其他运算,见下表:
运算符 | 左操作数 | 右操作数 | 结果类型 | 含义 |
---|---|---|---|---|
x + y 或y + x | time | duration | time | 对时间偏移一段时间 |
x - y | time | duration | time | 同上,只是反向偏移 |
x - y | time | time | duration | 时间之间的持续时间 |
x & y | date | time | datetime | 合并为日期时间 |
上面表格中的前三行,time
替换成date
、datetime
、datetimezone
仍然适用。
时间#time(24, 0, 0)
是一个非常特殊的时间,不能简单的认为是一整天或0。
#time(24, 0, 0)
单独使用时,可表示一整天。time
和duration
运算不会生成#time(24, 0, 0)
。如果数值上是,将会转换成#time(0, 0, 0)
。#time(24, 0, 0)
可能因为精度原因四舍五入生成。
// 1899/12/31 0:00:00
DateTime.From(#time(24, 0, 0))
// 2000/1/2 0:00:00
#time(24, 0, 0) & #date(2000, 1, 1)
// 1899/12/30 0:00:00
DateTime.From(#time(0, 0, 0) + #duration(0, 24, 0, 0))
// 1899/12/30 1:02:03
DateTime.From(#time(24, 0, 0) + #duration(0, 1, 2, 3))
// 1899/12/30 0:00:00
DateTime.From(#time(24, 0, 0) + #duration(0, 0, 0, 0))
// 1899/12/31 0:00:00
DateTime.From(Time.From(0.9999999999999))
时间值的原生类型是内部类型time
。
日期
日期值存储指定日期的不透明表现形式。日期表示范围1年1月1日
到9999年12月31日
。
日期值可以使用内部函数#date
构建:
#date(year, month, day)
参数取值范围:
- 1 ≤
year
≤ 9999 - 1 ≤
month
≤ 12 - 1 ≤
day
≤ 31
如果设置的日期不存在将会报错。
支持运算:
- 比较运算
??
- 其他运算,同
time
,见下表:
运算符 | 左操作数 | 右操作数 | 结果类型 | 含义 |
---|---|---|---|---|
x + y 或y + x | date | duration | date | 对日期偏移一段时间 |
x - y | date | duration | date | 同上,只是反向偏移 |
x - y | date | date | duration | 日期之间的持续时间 |
x & y | date | time | datetime | 合并为日期时间 |
日期值的原生类型是内部类型date
。
日期时间
日期时间值同时包含日期和时间。
日期时间值可以使用内部函数#datetime
构建。
#datetime(year, month, day, hour, minute, second)
参数取值范围:
- 1 ≤
year
≤ 9999 - 1 ≤
month
≤ 12 - 1 ≤
day
≤ 31 - 0 ≤
hour
≤ 24 - 0 ≤
minute
≤ 59 - 0 ≤
second
≤ 59
second
可以是小数,其他均为整数。
如果日期不存在,将会报错。
如果时间中的时设置为24时或者时间整体会舍入到24时(比如:23:59:59.99999999),将会报错。
支持运算:
- 比较运算
??
- 其他运算,同
time
,见下表:
运算符 | 左操作数 | 右操作数 | 结果类型 | 含义 |
---|---|---|---|---|
x + y 或y + x | datetime | duration | datetime | 对日期时间偏移一段时间 |
x - y | datetime | duration | datetime | 同上,只是反向偏移 |
x - y | datetime | datetime | duration | 日期时间之间的持续时间 |
日期时间值的原生类型是内部类型datetime
。
带时区的日期时间
带时区的日期时间值包含日期时间和时区。
时区部分是指从UTC时间偏移的时间,偏移的时间只能是-14:00到14:00之间。除了附带时区信息,datetimezone
和datetime
行为和属性基本一致。
带时区的日期时间使用内置函数#datetimezone
构建:
#datetimezone(
year, month, day,
hour, minute, second,
offset-hours, offset-minutes
)
参数取值范围:
- 1 ≤
year
≤ 9999 - 1 ≤
month
≤ 12 - 1 ≤
day
≤ 31 - 0 ≤
hour
≤ 24 - 0 ≤
minute
≤ 59 - 0 ≤
second
≤ 59 - -14 ≤
offset-hour
≤ 14 - -59 ≤
offset-minute
≤ 59
second
可以是小数,其他均为整数。
如果日期不存在,将会报错。
如果时间中的时设置为24时或者时间整体会舍入到24时(比如:23:59:59.99999999),将会报错。offset-hour
和offset-minute
组合后的值必须介于-14:00:00
和14:00:00
之间(含两端)。
支持运算:
- 比较运算
??
- 其他运算,同
time
,见下表:
运算符 | 左操作数 | 右操作数 | 结果类型 | 含义 |
---|---|---|---|---|
x + y 或y + x | datetimezone | duration | datetimezone | 对日期时间偏移一段时间(不考虑时区) |
x - y | datetimezone | duration | datetimezone | 同上,只是反向偏移 |
x - y | datetimezone | datetimezone | duration | 日期时间之间的持续时间(会考虑时区) |
注意事项:
- 与
duration
计算不会考虑时区。 - 相同类型相减,会考虑时区。
datetimezone
值存储时会考虑时区,比如#datetimezone(1, 1, 1, 0, 0, 0, 1, 0)
会报错。因为考虑时区信息,此时日期会越过可表示的日期范围。- 转换函数可能会考虑时区,需谨慎。
警告
通常不需要带时区的日期时间,因为数据基本都在一个时区内。但如果数据跨时区或者执行环境跨时区,那将会导致本地时间不一致。
本地时间(local time)是指事物所在地的时间,不同地区可能会因为时区不同,导致同一时刻的时间不同。
比如:
- 北京时间(东八区、UTC+8)2022-05-01T12:00:00+8:00
- 协调世界时(UTC)2022-05-01T04:00:00+0:00
- 纽约时间(西五区)2022-04-30T23:00:00-5:00
如果服务器位于纽约,而你需要北京时间,此时如果不使用时区,那么你很可能得到纽约时间。此时需要使用UTC时间,然后设置到成东八区,得到转换后的本地的时间。
带时区的日期时间值的原生类型是内部类型datetimezone
。
持续时间
持续时间存储以100ns
(1e-7s
)为单位的时间轴上两个时间点距离的不透明表示。
持续时间可以是正的,也可以是负的。使用64位有符号整数
存储表示的时钟周期(100ns
)。存储范围是-10675199天02:48:05.4775808
到106575199天02:48:05.4775807
。
持续时间使用内置函数#duration
构建。
#duration(day, hour, minute, second)
参数取值范围:
day
、hour
、minute
可以是任意整数。
second
可以是小数。
所有参数都可以设置正负。
// 10.00:00:00
#duration(0, 240, 0, 0)
// -2.00:39:54.7000000
#duration(2,-100,200,5.3)
// 0.00:58:00
#duration(0, 1, -2, 0)
支持运算:
- 比较运算
??
- 四则运算(见下表)
下表中T
表示time
、date
、datetime
、datetimezone
类型,且相同行是同一类型。
运算符 | 左操作数 | 右操作数 | 结果类型 | 含义 |
---|---|---|---|---|
x + y 或y + x | T | duration | T | 对T 偏移 |
x + y | duration | duration | duration | 持续时间求和 |
x - y | T | duration | T | 对T 偏移 |
x - y | T | T | duration | 两个T 的时间差 |
x - y | duration | duration | duration | 持续时间的差 |
x * y 或y * x | duration | number | duration | 持续时间的倍数 |
x / y | duration | number | duration | 持续时间的分数 |
持续时间值的原生类型是内部类型duration
。
文本
文本值表示Unicode字符序列。
文本值是字面值,可以直接输入(有时可能需要使用转义序列)。
text-literal:
"
text-literal-charactersopt"
text-literal-characters:
text-literal-character
text-literal-charactersopt
text-literal-character:
single-text-character
character-escape-sequence
double-quote-escape-sequence
single-text-character:
Any character except "
(U+0022
) or #
(U+0023
) followed by (
(U+0028
)
double-quote-escape-sequence:
""
(U+0022
, U+0022
)
支持运算:
- 比较运算
&
??
文本值的原生类型是内部类型text
。
二进制
二进制值表示字节序列。
二进制值可以使用内部函数#binary
构建。
#binary({0x00, 0x01})
#binary("QUJD")
#binary
可以使用的参数
- 字节列表。列表每个值介于0-255。
- 文本。使用BASE64编码的文本。
支持运算:
- 比较运算
??
二进制值的原生类型是内部类型binary
。
列表
列表值是枚举时产生一系列值的值。列表中可以包含任意类型的值。
列表初始化语法:
list-expression:
{
item-listopt }
item-list:
item
item ,
item-list
item:
expression
expression ..
expression
初始化示例
{1, "A", error "error message"}
{1..10}
{1, 2..5, 6, 7, 8..11}
// 必须是单个Unicode字符
{"A".."H"}
{"你".."我"}
- 列表索引值以
0
开始。 - 列表可以包含无限个元素,如果对无限个元素的数组求元素个数,可能会报错或无法中止。
- 项在未访问之前不会计算。
- 虽然使用初始化语法构造的列表会根据
item-list
中的顺序生成项,但通常,从库函数返回的列表每次枚举时可能会生成不同的组合或不同数量的值。 - 比较两个列表是否相等时会考虑顺序
支持运算
&
=
和<>
??
列表值的原生类型是内部类型list
,即项类型为any
的列表。
记录
记录是字段的有序序列,字段由字段名和字段值组成。字段名是在记录中定义字段的特殊文本值(标识符)。字段值可以是任何类型的值。
记录初始化语法:
record-expression:
[
field-listopt ]
field-list:
field
field ,
field-list
field:
field-name =
expression
field-name:
generalized-identifier
quoted-identifier
当评估记录表达式时:
- 分配给每个字段名的表达式用于确定关联字段的值(即等号右侧的表达式用于指定字段值)。
- 如果分配给字段名的表达式在计算时生成一个值,则该结果为记录的字段值。
- 如果分配给字段名的表达式在计算时引发错误,则引发错误的事实和引发的错误值一起记录在该字段中。对该字段的后续访问都会使用记录的错误值重新引发错误。
- 表达式在类似于父环境的环境中进行评估,使用记录中除了正在初始化的字段的每个字段名进行合并。
- 在相应字段访问之前,不会计算它在记录中的值。
- 记录中的值最多计算一次。
- 表达式的结果是一个包含空元数据记录的记录。
- 记录中的字段顺序由它们在初始化表达式中出现的顺序定义。
- 指定的每个字段名在记录中必须是唯一的,否则将引发错误。变量名使用序数比较(根据编码比较)。
其他事项
- 比较两个记录时,不考虑字段顺序。
支持运算:
=
和<>
&
??
记录值的原生类型是内部类型record
,即打开的空字段的记录。
表
表值是行的有序序列。行是值的有序序列。表的类型决定了表中所有行的长度、表列的名称、表列的类型以及表键(如果有)的结构。
表值可以使用内部函数#table
构建。
#table({"A", "B"}, { {1, 2} })
#table(
type table [A=number, B=text],
{ {1, "X"}, {2, "Y"}, {3, "Z"} }
)
支持运算:
=
和<>
&
??
比较两个表时会考虑行顺序,不考虑列顺序。
用&
连接表时,如果有缺失值(比如两个表列名不完全同),将会填充null
值。
表值的原生类型是自定义表类型(从内部类型table
派生),即列类型为any
,没有键。
type table[...]
函数
函数值是一种特殊的值,它可以被调用。
支持运算:
=
和<>
??
函数等于本身,即使更换了类型依然相等。不同的函数即使定义一样也可能不相等。
函数值的原生类型是自定义函数类型(从内部类型function
派生)。
类型
类型值是描述其他值类型的值。
支持运算:
=
和<>
??