- 撰稿:宗喆
- 原文:https://mp.weixin.qq.com/s/Jl…
- KusionStack: https://github.com/KusionStack/kusion
- KCLVM: https://github.com/KusionStack/KCLVM
1. 背景
最近在参加 KusionStack 内置的畛域语言 —— KCL 配置语言编译器 的开发,语言的语法中包含一个“索引签名”的概念,在参加社区探讨的时候发现很多小伙伴不明确这个“索引签名”是什么,于是本人也想了一下,发现自己也只是晓得是什么样子,然而不晓得“索引签名”残缺的定义,因而,决定写一篇贴子来梳理一下“索引签名”到底是什么。
2. 见名知意
首先,索引签名的想法并不神秘陈腐。晚期 Windows 开发中应该见过相似的编程标准:
- bool(BOOL) 用 b 结尾 bIsParent
- byte(BYTE) 用 by 结尾 byFlag
- short(int) 用 n 结尾 nStepCount
- long(LONG) 用 l 结尾 lSum
- char(CHAR) 用 c 结尾 cCount
只有看到变量和类成员的名字就晓得其类型,进步了代码类型方面的可读性。然而这种约定并没有积淀到 C ++ 语言中,如果语言可能反对定义 以 b 结尾的成员是 BOOL 类型 这种个性就厉害了——这其实就是 索引签名 的浮夸目标。
先从字面意义上看,“索引签名(index signature)”蕴含“索引(index)”和“签名(signature)”两局部。
2.1 索引(index)
从开发人员的角度来看,索引,相似于 C 语言中指针,它像一个箭头一样能够指向一个特定的事物,出于某些起因咱们可能无奈间接拜访这个事物,或者这个事物与其余的货色混合在一起,间接拜访这个事物可能要在很多其余事物中寻找很久。因而,咱们应用索引指向这个事物,能够了解为咱们在咱们须要的事物上绑了一根线,并且留了一个线头在身边,每当咱们想要应用这个事物的时候,咱们不须要再从一堆事物中去寻找它,只须要拿起这个线头,并顺着这根线就能找到这个特定的事物,这个线头就是 ” 索引 index”,并且很显著,一根线不容许分叉绑定两个事物,所以通常大家默认某个事物的 ” 索引 index” 是不会再指向另一个事物的。
因而,在开发的过程中,“索引 index”最次要的应用场景就是“在一堆事物中,查找特定的事物”。例如:最常见的数据结构 - 数组,就是“索引”的一个优良案例。在数组中,索引是一个整形的数字,这个数字是数组中每个元素的地位信息,通过地位,疾速的定位某个数组元素。
int a[3] = [1, 2, 3];// 应用索引 0,就能够在 1,2,3 三个数字中,疾速的找到排在最后面的元素。assert a[0] = 1;
assert a[1] = 2;
assert a[3] = 3;
除了数组,另一个应用索引的数据结构是咱们常见的 Hash 表,只不过在有些编程语言中将哈希表中的索引叫做 key。
hashtable<string, string> a;
// "Jack" 就能够视作一个索引, 通过名字字符串作为索引,// 在不思考重名的状况下,它指向了一个构造实例 Person("Jack")。a.put("Jack", new Person("Jack"))
a.put("Tom", new Person("Tom"))
a.put("Lee", new Person("Lee"))
再举一个例子,很多编程语言中都存在的构造 struct 或者类 class,也应用到了索引的思维。
// 能够看做是 String 和 Integer 的汇合
// 如果没有索引,咱们就只晓得 Person 外部有两个属性,// 一个类型为 String 示意名字,// 一个为 Integer 示意年龄。struct Person{
name: String,
age: Integer,
}
Person p = new Person(name: "Jack", age: 10);
// 通过索引 name 咱们可能轻松的获取到 Person 的名字 Jack。assert p.name == "Jack"
// 通过索引 age 咱们可能轻松的获取到 Person 的年龄 10。assert p.age == 10
综上,索引能够被看作一个指针,没有具体的格局束缚,只有能惟一的指向一个事物即可,不能具备二义性,即不能指向 A 的同时又指向 B。或者索引也能够看作一个办法,以索引值为参数,返回索引指向的事物。
注:这个概念不包含一些非凡状况,比方某些利用场景就是须要同时指向 A 和 B 的索引也是有可能的,这里探讨的是大多数的通用状况。
2.2 签名(Signature)
在编程语言畛域,Signature 这个词除了应用在 IndexSignature 中,在很多常见的编程语言中也有 Signature 这个概念。比方 C ++ 中的类型签名:
char c;
double d;
// 他的签名为 (int) (char, double)
int retVal = (*fPtr)(c, d);
通过下面这个类型签名,咱们尽管不晓得这个函数指针将来可能会指向的函数的具体定义,然而通过这个签名,咱们能看到这个指针指向的函数如何应用,它以 char 和 double 为传入参数,返回值为 int,并且,这个签名也对指针将来指向的函数进行了束缚,它只能指向以 char 和 double 为传入参数,返回值为 int 的函数。类似的概念在 Rust 语言中也有体现。在 Rust 中,咱们能够间接应用一个函数的签名如下:
// add 办法的签名 fn(i32, i32) -> i32
fn add(left: i32, right: i32) -> i32 {left + right}
// sub 办法的签名 fn(i32, i32) -> i32
fn sub(left: i32, right: i32) -> i32 {left - right}
// 通过办法签名,咱们能够为某一类构造相近的办法提供工厂。fn select(name: &str) -> fn(i32, i32) -> i32 {
match name {
"add" => add,
"sub" => sub,
_ => unimplemented!(),}
}
fn main() {let fun = select("add");
println!("{} + {} = {}", 1, 2, fun(1, 2));
}
再来看看 Java 中的类型签名:
能够看到,外围的思维与 C /C++/Rust 中的类型签名一样,通过形容办法的传入参数与返回值的类型,来概述一个办法如何应用,而不须要关怀这个办法的具体实现。
Python/Golang 中也有类型签名的概念,而且外围思路都一样,这里就不再赘述了。
通过理解这些编程语言的类型签名,咱们晓得,签名 (Signature) 其实与类型 (Type) 形容了同一个事物,类型 (Type) 所形容的事物是某些性质的汇合,具备雷同性质的事物,就能够认为它们的类型 (Type) 雷同;而签名 (Signature) 能够看作由多个类型 (Type) 组合而成的复合类型。
举例说明:
int32 a = 0; // a 的类型 (type) 是 int32
能够看到上述变量 a 的类型 (type) 是 int32,大家只有一听到 int32,就会条件反射的想到 a 的一些性质,比方:32 位,整数,范畴等等,int32 就是对这些性质的总称,下次再遇到一个变量 b,只有他的性质合乎 int32 的性质,咱们就能够把它们归为一类,即,它们都是类型 (type) 为 int32 的变量。
可是,编程语言的类型零碎中,不仅仅有变量,还有一个很重要的货色 – 办法。
int add(int a, int b) {return a+b;}
当初,就须要一个货色,形容下面这个办法的类型,即,须要有一个货色来辨别什么样子的办法与 add 办法属于同一类。名称?恐怕不行,因为上面这两个同名办法用起来感觉齐全不一样。
// 两个数相加
int add(int a, int b) {return a+b;}
// 两个数组合并
int[] add(int a[], int b[]) {return a.append(b);
}
所以,在大佬们设计语言的的时候决定,应用返回值和参数列表的类型 (type) 组合起来,来定义一个办法的类型,即:
// 两个数相加
// 类型为 (int, int) -> int
int add(int a, int b) {return a+b;}
// 两个数组合并
// 类型为 (int[], int[]) -> int[]
int[] add(int a[], int b[]) {return a.append(b);
}
而签名 (Signature) 就能够了解为将多个类型 (type) 组合起来造成的复合类型。这个签名用来形容办法的类型,就能够叫做办法签名(Method/Function Signature)。那么,写到当初,通过类比,也能猜出索引签名大略是个什么货色了,后面提过索引能够看做是一个办法,输出一个值,返回它指向的事物。
2.3 索引签名(IndexSignature)
下面提到,索引咱们能够看作一个指针或是一个办法,签名 (Signature) 就能够了解为将多个类型 (type) 组合起来造成的复合类型,索引签名 (IndexSignature) 形容的就是索引的类型,写到这里我脑子里产生了点疑难,那索引签名不就是索引的类型吗,索引为什么要应用复合类型进行形容,一个一般类型 (type) 形容不了索引的类型吗?
- a[0] 这个索引的类型不就是 Integer 吗?
- hash.get(“name”) 这个索引的类型不就是 String 吗?
这个问题,源自于对索引了解的偏差,
a[0]
的索引不是 0,他的索引是0->a[0]
`, 即输出 0,返回[0]
.hash.get("name")
的索引也不是“name”, 他的索引是“name”->"Jack"
, 输出“name”返回 ”Jack”。
写到这里,其实应用各种编程语言的小伙伴们心里应该都能感觉本人可能或多或少都接触过索引签名这个货色,只是过后并不在意他叫什么,之所以这么说,是因为我本人在写到这里的时候,想到了之前开发 java 的时候应用的 hashmap:
public class RunoobTest {public static void main(String[] args) {HashMap<string, string> Sites = new HashMap<string, string>();
Sites.put("one", "Google");
Sites.put("two", "Runoob");
Sites.put("three", "Taobao");
Sites.put("four", "Zhihu");
System.out.println(Sites);
}
}
上述代码第 7 行 HashMap<string, string> Sites = new HashMap<string, string>()中,<string, string> 就能够了解为一种索引签名,它定义了这个 HashMap 构造中的索引的类型,是输出一个字符串,返回一个字符串。而数组的索引签名也相似,只不过,数组的索引签名编译器帮咱们主动省略了,即输出类型肯定是 int,不必咱们手动书写了。
// 显式索引签名:Array<int, int> a = [0, 1, 2]
int[] a = [0, 1, 2];
// 显式索引签名:Array<int, String> a = ["0", "1", "2"]
String[] a = ["0", "1"];
3. 一些语言中的索引签名
索引签名的思维由来已久,最早甚至能够追溯到早些年间程序员们为了程序的可读性而定下的编程规约,当咱们规定一个整型变量的名称必须以 i 结尾的时候,其实曾经是在定义指向一个整型的的索引的签名了。
int i_user_id = 10; // 整型以 i 结尾,定义了 <i 结尾的字符串, int> 的索引签名
float f_user_weight = 120.3; // 浮点以 f 结尾,定义了 <f 结尾的字符串, float> 的索引签名
不过,规约可能并不是所有人都违心恪守,当索引的名称成为编程元素的一部分,并且能够动静的操作的时候,将索引签名作为变成规约,就不是太适合了。
// 当呈现能够动静增加索引的编程元素。const a = {"name": "Jack"}
// 你和你的小伙伴约定好,年龄的索引就是“age”。// 他在某个中央 add("age", 10)。a.add("age", 10);
// 你在某个中央, 须要这个年龄。a.get("age");
// 如果索引签名是编程规约,而不带有强制性。// 你的小伙伴恰好手一滑,眼一闭,写错了也没看到 warning。a.add("aeg", 10);
// 那你这边就只能看到空指针异样了。NullPointerException: "age" is not define.
因而,为了晋升程序的稳定性,防止这种不必要的危险,一些通用编程语言 (如:TypeScript) 和畛域语言 (如:KCL,CUE) 开始将索引签名作为语言的个性裸露给开发者,旨在提供编程过程中的安全性和稳定性,升高上述问题产生的影响。
3.1 TypeScript 索引签名
在 TS 中,咱们能够通过上面这种形式定义一个对象:
const salary1 = {
baseSalary: 100_000,
yearlyBonus: 20_000
};
依据上文咱们对索引的形容,咱们晓得这个对象有两个索引,并且它们的类型即索引签名应该是雷同的,即它们是同一类索引。
const salary1 = {
baseSalary: 100_000, // 索引 1 : 输出“baseSalary”,返回 100_000
yearlyBonus: 20_000 // 索引 2 : 输出”yearlyBonus“,返回 20_000
};
TS 提供了一种个性,使得开发者能够编写这种索引签名,
interface NumbersNames {[key: string]: string // 索引的类型为输出 String,返回 String
}
const names: NumbersNames = {
'1': 'one',
'2': 'two',
'3': 'three',
// etc...
'5': 'five' // Error: 这个索引的输出类型为 int,类型不匹配。};
3.2 CUE 索引签名
CUE 反对在索引签名中写正则表达式,反对对索引名称的校验。
a: {
foo: string // 索引 foo 返回值是 string 类型。[=~"^i"]: int // 以 i 结尾的索引,返回值都是 int。[=~"^b"]: bool // 以 b 结尾的索引,返回值都是 bool。...string // 其余的所有的索引返回值都是 string。}
b: a & {
i3: 3 // 索引 i3 以 i 结尾,返回值是 3 类型为 int。bar: true // 索引 bar 以 b 结尾,返回值 true 类型为 bool。other: "a string" // 其余索引的返回值类型都是字符串。}
3.3 KCL 索引签名
KCL 索引签名的模式为 [<attr_name>: <index_type>]: <value_type>,语义上示意构造中所有属性的 key 只能为 <index_type> 类型,值只能为 <value_type>
类型
- 索引签名的
<index_type>
` 处的类型只能为 str, int, float,不能为 union 类型 <value_type>
能够为 KCL 任意非法的数据类型,蕴含 schema 类型和 union 类型等类型<attr_name>
示意任意 KCL 非法的标识符,并且能够省略不写,个别用于和 check 联合应用
根底用法
schema 定义形式
schema Map:
[str]: str
- 留神应用了索引签名的 schema 默认为 relaxed。
- 一个索引签名只能在 schema 当中定义一次。
高级用法
类型签名书写默认值
schema Map:
[str]: str = {"default_key": "default_value"}
与 schema 定义混合应用,强制 schema 所有属性 key, value 类型:
schema Person:
name: str
age: int # error, 与[str]: str 语义抵触,[str]: str # schema 所有属性的值只能为字符串类型
能够在 schema 中同时定义 schema 属性和索引签名,通常用于示意 schema 中额定属性的类型束缚,强制除 schema 定义所有属性 key, value 类型。
schema Person:
name: str
age?: int
[...str]: str # 示意除 name, age 之外,其余 schema 属性必须为字符串类型,属性的值也必须为字符串类型
属性名称配合 check 应用
schema Data:
[dataName: str]: str
check:
dataName in ["Alice", "Bob", "John"]
data = Data {
Alice: "10"
Bob: "12"
Jonn: "8" # error Jonn not in ["Alice", "Bob", "John"]
}
- 留神:KCL 索引签名暂不反对 union 类型以及字面量类型等类型。
- 留神:索引签名暂不反对对 value 的的值进行校验,仅反对类型查看。
- 留神:索引签名暂不反对相似 CUE 的正则校验[“$b^”],因为属于 runtime 查看,不属于类型零碎的一部分,当类型查看从 runtime 阶段前置后不容易联合,因而暂不反对。
4. 总结
本文简略介绍了索引签名,通过梳理索引和签名的概念,并比照了一些通用编程语言和畛域语言中应用到的签名的思维,泛泛的形容了一下索引签名大略的样子,心愿可能帮忙大家可能更加轻松的理解索引签名这个概念,文章的内容只是笔者集体对索引签名的了解,如果有不对或者不适合的中央欢送大家斧正。
参考链接
- TypeScript – https://www.typescriptlang.org/
- KCL – https://github.com/KusionStack/KCLVM
- CUE – https://cuelang.org/
- Index – https://www.computerhope.com/jargon/i/index.htm
-
Java Type Signature
- https://en.wikipedia.org/wiki/Type_signature#Java
- https://docs.oracle.com/javase/10/docs/api/com/sun/jdi/doc-files/signature.html
- Java Method Signature – https://www.scaler.com/topics/method-signature-in-java/
- Function Signature – https://developer.mozilla.org/en-US/docs/Glossary/Signature/Function
- 说说我对 TypeScript 索引签名的了解 – https://segmentfault.com/a/1190000040727281
- KCL 索引签名 – https://kusionstack.io/docs/reference/lang/lang/tour/#%E7%B4%A2%E5%BC%95%E7%AD%BE%E5%90%8D
- Rust Function Signature – https://stackoverflow.com/questions/42157511/what-is-a-function-signature-and-type