2.28. 泛型编程

Daslang 允许在语句、函数和函数声明中省略类型,使其类似于动态类型语言(如 Python 或 Lua)的编写。 所述函数在第一次调用时针对特定类型的参数 * 实例化* 。

还有一些方法可以检查提供的参数的类型,以便更改函数的行为,或在编译阶段提供合理有意义的错误。 这些方法中的大多数都是通过 s 实现的

与 C++ 及其 SFINAE 不同,您可以使用通用条件 (if) 来根据其参数的类型信息来更改函数的实例。 请考虑以下示例:

def setSomeField(var obj; val) {
    if ( typeinfo has_field<someField>(obj) ) {
        obj.someField = val
    }
}

这个函数在提供的参数中设置 someField 如果它是一个带有 someField 成员的结构体。

我们可以做得更多。 例如:

def setSomeField(var obj; val: auto(valT))
    if ( typeinfo has_field<someField>(obj) ) {
        if ( typeinfo typename(obj.someField) == typeinfo typename(type<valT -const>) ) {
            obj.someField = val
        }
    }

这个函数在提供的参数中设置 someField 如果它是一个带有 someField 成员的结构体,并且只有当 someFieldval 是同一类型时!

2.28.1. typeinfo

大多数类型反射机制都是通过 typeinfo 运算符实现的。有:

  • typeinfo typename(object) // 返回 Object 的 typename

  • typeinfo fulltypename(object) // 返回 Object 的完整 typename,其中包含 Contract (如 !const, 或 !&)

  • typeinfo sizeof(object) // 返回 sizeof

  • typeinfo is_pod(object) // 如果对象为 POD 类型,则返回 true

  • typeinfo is_raw(object) // 如果对象是原始数据,即可以使用 memcpy 复制,则返回 true

  • typeinfo is_struct(object) // 如果 object 为 struct,则返回 true

  • typeinfo has_field<name_of_field>(object) // 如果 object 是字段为 name_of_field 的结构,则返回 true

  • typeinfo is_ref(object) // 如果 object 是对某物的引用,则返回 true

  • typeinfo is_ref_type(object) // 如果 Object 是引用类型(例如 Array、Table、das_string 或其他已处理的引用类型),则返回 true

  • typeinfo is_const(object) // 如果 object 是 const 类型(即无法修改),则返回 true

  • typeinfo is_pointer(object) // 如果 Object 是指针类型,即 int?

所有 typeinfo 都可以使用 type 关键字处理类型,而不是对象:

typeinfo typename (type<int>) // 返回 "int"

2.28.2. auto 和 auto(named)

在泛型中不必省略类型名称,而是可以使用显式的 auto 类型或 auto(name) 来键入它:

def fn(a: auto): auto {
    return a
}

def fn(a: auto(some_name)): some_name {
    return a
}

这与以下相同:

def fn(a) {
    return a
}

如果函数接受大量参数,并且其中一些参数必须属于同一类型,这将非常有用:

def fn(a, b) { // a  b 可以是不同的类型
    return a + b
}

这与以下不同:

def fn(a, b: auto) { // a  b 是一种类型
    return a + b
}

此外,请考虑以下事项:

def set0(a, b; index: int) { // a 只应该是数组类型,与 b 的类型相同
    return a[index] = b
}

如果使用 floats 数组和 int 调用此函数,则会收到不太明显的编译器错误消息:

def set0(a: array<auto(some)>; b: some; index: int) { // a 是数组类型,与 b 类型相同
    return a[index] = b
}

命名 autotypeinfo 的用法

def fn(a: auto(some)) {
    print(typeinfo typename(type<some>))
}

fn(1) // 打印 "const int"

您还可以使用 delete 语法修改类型:

def fn(a: auto(some)) {
    print(typeinfo typename(type<some -const>))
}

fn(1) // 打印 "int"

2.28.3. 类型协定和类型操作

泛型函数参数、result 和推断的类型别名可以在推理期间进行作。

const 指定常量和正则表达式将匹配:

def foo ( a : Foo const )   // 接受 Foo  Foo const

==const 指定表达式的 const 必须与参数的 const 匹配:

def foo ( a : Foo const ==const )   // 仅接受 Foo const
def foo ( var a : Foo ==const )     // 仅接受 Foo

-const 将从匹配类型中删除 const:

def foo ( a : array<auto -const> )  // 匹配任何具有 non-const 元素的数组

# 指定只接受临时类型:

def foo ( a : Foo# )    // 仅接受 Foo#

-# 将从匹配类型中删除临时类型:

def foo ( a : auto(TT) ) {      // 接受任何类型
    var temp : TT -# := a       // TT -# 现在是常规类型,当 `a` 是临时的时,它可以将其克隆到 `temp` 中
}

& 指定参数通过引用传递:

def foo ( a : auto& )           // 接受通过引用传递的任何类型的

==& 指定表达式的引用必须与参数的引用匹配:

def foo ( a : auto& ==& )   // 接受通过引用传递的任何类型的 (例如,变量 i,即使其整数)
def foo ( a : auto ==& )    // 接受按值传递的任意类型(例如值 3)

-& 将从匹配类型中删除引用:

def foo ( a : auto(TT)& ) {     // 接受通过引用传递的任何类型的
    var temp : TT -& = a        // TT -& 不是本地引用
}

[] 指定参数是任意维度的静态数组:

def foo ( a : auto[] )          // 接受任何类型、任何大小的静态数组

-[] 将从匹配类型中删除静态数组维度:

def take_dim( a : auto(TT) ) {
    var temp : TT -[]           // temp is type of element of a
}
// 如果 a 为 int[10],则 temp 为 int
// 如果 a 为 int[10][20][30],则 temp 仍为 int

implicit 指定临时类型和常规类型都可以匹配,但类型将被视为指定类型。`implicit`是 _UNSAFE_:

def foo ( a : Foo implicit )    // 接受 Foo  Foo#,则 a 将被视为 Foo
def foo ( a : Foo# implicit )   // 接受 Foo 和 Foo#,则 a 将被视为 Foo#

`explicit`指定不应用 LSP,只接受完全类型匹配:

def foo ( a : Foo )             // 接受 Foo 和直接或间接从 Foo 继承的任何类型
def foo ( a : Foo explicit )    // 仅接受 Foo

2.28.4. 选项

可以将多个选项指定为函数参数:

def foo ( a : int | float )   // 接受 int  float

可选类型始终使函数成为泛型。

泛型选项将按列出的顺序进行匹配:

def foo ( a : Bar explicit | Foo )   // first 将尝试精确匹配 Bar,而不是从 Foo 继承的任何其他内容

|# shortcat 匹配以前的类型,临时翻转:

def foo ( a : Foo |# )   // 按顺序接受 Foo 和 Foo#
def foo ( a : Foo# |# )  // 按该顺序接受 Foo# 和 Foo

2.28.5. typedecl

请考虑以下示例:

struct A {
    id : string
}
struct B {
    id : int
}
def get_table_from_id(t : auto(T)) {
    var tab : table<typedecl(t.id); T>  // 注意 typedecl
    return <- tab
}

[export]
def main {
    var a : A
    var b : B
    var aTable <- get_table_from_id(a)
    var bTable <- get_table_from_id(b)
    print("{typeinfo typename(aTable)}\n")
    print("{typeinfo typename(bTable)}\n")
}

此处使用 provided struct 的 id 字段的键类型创建 table。 此功能允许根据提供的表达式类型创建类型。

2.28.6. 泛型元组和类型<>表达式

Consider the following example:

tuple Handle {
    h : auto(HandleType)
    i : int
}

def make_handle ( t : auto(HandleType) ) : Handle {
    var h : type<Handle> // 注意 type<Handle>
    return h
}

def take_handle ( h : Handle ) {
    print("count = {h.i} of type {typeinfo typename(type<HandleType>)}\n")
}

[export]
def main {
    let h = make_handle(10)
    take_handle(h)
}

在函数 make_handle 中,变量 h 的类型是使用 type<> 表达式创建的。 type<> 在 context 中推断(这次基于函数参数)。 此功能允许根据提供的表达式类型创建类型。

泛型函数 take_handle 采用任何 Handle 类型,但只接受 Handle 类型元组。

这与 C++ 模板系统有一些相似之处,但由于元组是弱类型,因此受到更多限制。