蓝狮注册登陆编程语言中索引签名是什么?

  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 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)组合而成的复合类型。

0 Comments
Leave a Reply