• 撰稿:宗喆
  • 原文: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) -> i32fn add(left: i32, right: i32) -> i32 { left + right }// sub 办法的签名 fn(i32, i32) -> i32fn 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) -> intint 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