小言_互联网的博客

dip1034加底部类型

249人阅读  评论(0)

原文地址
作者:丹尼斯

抽象

底层类型来解决d类型系统的部分漏洞和限制.
对表示运行时错误/非终止函数有用.由于用例少,已拒绝先前的dip1017.本dip来补充.

内容

背景

纯函数,代表输入->输出,而计算机,还可能永远计算.因而加上个来表示函数计算过程中,返回/赋值失败.如:

bool isPrime(int x);
bool foo(int x) {
   
    return isPrime(x);
}

虽然bool只返回真/假,但还可能未计算完(出错了).用表示无穷循环/崩溃(内存不足)/抛异常等.有了,并没有加底层值或有运行时开销.
不应与等单元类型混淆,如:

void assertInBounds(int[] arr, int x);
void foo(int[] arr, int x) {
   
    return assertInBounds(arr, x);
}

单元类型仅含1个值,不需要存储.
调用assertInBounds返回2样:{ (), }.
或者()或者表崩溃.
总是崩溃的函数:

auto foo() {
   
    assert(0);
}

仅返回,但编译器推导为,这是错的.

原理

显示部分示例.在后几节用无中表示.

跨函数分析流

当前,d识别后语句,当(1);等无穷语句及断定(0)后代码为死码.因而可省略中语句.

int main() {
   
    while(true) {
   }
    //不必返回,不可达
}

作为语句时没问题,但基于表达式时,函数作为边界,就丢失分析流信息了.

struct Expected(T, E) {
   
    private SumType!(T, E) data;

    T value() {
   
        return data.match!(
            (T val) => val,
            (E err) {
    throw new Exception(err.to!string); }//推导为空,不能转为T,因而不编译.
        );
    }
}

不编译,推导第2个函数返回类型为,而不是T.虽然后端知道不返回,但前端要确保表达式/模板实例为正确实例,因此不编译,但如果搞成,则可编译.
还有个,λ语法如(T val) => val不能用于(E e)处理,因为d只有抛语句,没有抛表达式.
表达式都要求类型,但无类型,因而,不能在λ.当有个兜底类型时,就可以了.

void foo(int function() f) {
   }
void main() {
   
    foo(() => throw new Exception());
}

其他示例
语句中,可用默认:断定(0),而不能在λ这样表示,同样是没有返回类型.

alias Var = SumType!(int, double, string);
int round(Var v) {
   
    return v.match!(
        (int x) => x;
        (double x) => cast(int) x;
        other => assert(0); //当前不编译
    );
}

有了底层类型,可以用std.exception: handle异常=>错误,从而不用试/抓块.

auto s = "10,20,30"; //好整
auto r = s.splitter(',').map!(a => to!int(a));
auto h = r.handle!(ConvException, RangePrimitive.front, (e, r) => assert(0));

也可用于三元操作符?:.

noreturn abort(const(char)[] message);

int fun() {
   
    int x;
    //...
    return x != 0 ? 1024 / x : abort(0, "不用计算.");
}

空数组字面类型

当前不能隐式转换void[]为其他类型数组.

int[] test() {
   
    return cast(void[]) [1, 2, 3];
}

如下:

错误:不能隐藏转换`void[]`至`int[]`

但,void[][]字面,却可以

int[] test() {
   
    pragma(msg, typeof([])); // void[]
    return []; //无误.
}

这是编译器允许特例,自定义时就不行:

import std;

struct CastToIntArray {
   
    int[] arr;
    void opAssign(T)(T[] other) {
   
        arr = other.map!(x => cast(int) x).array;
    }
}

void main() {
   
    CastToIntArray a;
    a = [2.1, 3.1, 4.1];
    writeln(a); // [2, 3, 4]
    a = []; //不能实例化.
    writeln(a);
}

如果把[](空数组)看作底层类型,则可转为任意类型的空数组.如用无用[]而不是空[]来实例化赋值操,则map返回如下:

struct MapResult {
   
    noreturn[] storage;//无中,可返回任意类型

    int front() {
   
        return cast(int) storage[0];
    }//如果返回为`动`,可静态推导为总是错误

    void popFront() {
   
        storage = storage[1..$];
    }

    bool empty() {
   
        return (storage.length == 0);
    }
}

空的()函数,可折叠常数(优化).

空针类型,空针型

当前typeof(null)是特殊类型.其为指针/类/数组子类型,但不能当作任意类型指针.

void foo(T)(T* ptr) {
   }

void main() {
   
    foo(new int); // 好, T = int
    static assert(is(typeof(null): int*)); // okay
    foo(null); //不能推导
}

目前不能有typeof(*null).因而期望is(typeof(*null) == noreturn)is(typeof(null) == noreturn*).

不中函数

d有个内部不中函数列表,不中,在优化上有优势.但这样搞,不能扩展.如c退出().如果有无中,则:

extern(C) noreturn exit();//无中

可同类型系统交流,并允许优化.

标准名

dip无中来表明这个特点.与zig/c++一样.
typeof([]) == noreturn[],无中更直接.
一个特例是:

auto x = [];
x ~= 3; //不能从[](空数组)推导类型

noreturn[]也叫typeof([])noreturn*也叫typeof(null).

先前工作与其他语言

dip1017
rust:!表示never.
ts:never类型.用于动态类型错误.

function foo(value: string | number) {
   
    if (typeof value === "string") {
   
        value; // 串
    } else if (typeof value === "number") {
   
        value; // 数字
    } else {
   
        value; // 错误类型.
    }
}

zig:不可达无中.

switch(value) {
   
    case x => 0;
    case y => 1;
    case z => unreachable;
    else unreachable;
}

d等价版:

switch(value) {
   
    case x: return 0;
    case y: return 1;
    case z: assert(0);
    default: assert(0);
}

d目前不能在基于表达式中用断定(0).zig基于表达式的.

描述

(0)已加底层类型至D
用于表示中止程序的表达式的类型.
null底层类型指针[]底层类型数组.
如下可访问他们:

typeof(*null);
typeof([][0]);
typeof(assert(0));
typeof(throw new Exception()); //依赖(4)

(1)类似串/大小型,隐式为该型添加一个标准别名至每个模块.

该别名为noreturn(无中).

alias noreturn = typeof(*null);

(2)所有可重载内置符号有相应无中版本特化.
assert(0) + assert(0)导致歧义:是int, long, floatdouble版本加?
但,现在是is(typeof(assert(0) + assert(0)) == noreturn).
[1, 2, 3] ~ assert(0),是连接int还是int[]?
因为noreturn是任意类型的子类,有歧义.但实际不重要,因而结果类型为整[],并编译为:

auto __tmp = [1, 2, 3];
assert(0);

(3)允许隐式从无中转为其他任意类型.
但不能隐式转为noreturn(无中),无中=>任意,但反过来不成立.即is(noreturn : T)true.重载函数的匹配级可隐式转换匹配.
定义无中的新协变规则,对T:

/* 1 */ is(noreturn[] : T[])
/* 2 */ is(noreturn* : T*)
/* 3 */ is(noreturn function(S) : T function(S), S...)
/* 4 */ is(noreturn delegate(S) : T delegate(S), S...)

1和2确保可相应隐式转换null[]指针/数组.
3和4确保不用转换,直接传递noreturn function() exitint function() callback参数.即无中函数选择性大.

注意:is(noreturn:T)不成立,因为在元素类型上,指针/数组当前不协变.而函数指针返回类型上不协变.协变类似kt协变.
即,虽然class C : Object,但is(C[] : Object[])不成立,就代表不协变.
同样is(dchar*:int*) == falseis(char function() : ubyte function()) == false.

当前语言,可隐式转换typeof(null)数组/函数指针/闭包/类/接口,因为他们的类型都有null值.
因而,在提出dip后,当typeof(null)变成noreturn*时这二者一样类型,is(typeof(null) : int[])is(typeof(null) : void function())仍为真.

(4)加抛表达式替换抛语句.

throw(一元抛)cast()(转换())是同级运算.
一般类似throw new Exception(),但仍可能要消歧,如:

throw E0 + E1 => (throw E0) + E1
throw E0 = E1 => (throw E0) = E1

(5)如每条路径均为死循环或无中类型,则动可推导为无中.

控制流退出函数时按而不是noreturn(无中),因为,人们习惯了.

//返回`void`
auto main() {
   
    import std;
    writeln("你好");
}

// 返回`noreturn`
auto foo(int x) {
   
    if (x > 0) {
   
        throw new Exception("");
    } else if (x < 0) {
   
        while(true) {
   }
    } else {
   
        cast(noreturn) main();
    }
}

noreturn(无中)的属性:

noreturn.mangleof == "b";

随意的,只是b是还没用的数字字符.

当前,typeof(null).mangleof == “n”.将改为"pb",底层指针.
不同d版本编译时,可能有问题.
但,在模板外很少用typeof(null)的签名.大小:

noreturn.sizeof == 0;

搞成0,直接点.不折腾.

noreturn.alignof == 0;

对齐为0.强制T*0,适合typeof(null).

noreturn.init == assert(0);

noreturn(无中)无值,所以无初值(init),因而底层,出现noreturn.init时,就是assert(0).

noreturn*.sizeof == size_t.sizeof;
noreturn[].sizeof == size_t.sizeof * 2;

尽管noreturn*不必存储,但与其他指针一样大.
typeof(null).sizeof==size_t.sizeof一致.
typeof(null)一样,也不需要存储typeof([]).
T[]应与构一致.

struct slice(T) {
   
    size_t length;
    T* ptr;
}

同其他语言特征交互

先前的dip1017用例太少,而现在允许用底部类型,则可在表达式中用断定(0).
声明

带初化的用noreturn(无中)定义变量,在活动时,简单生成.而不带初化,则访问变量时,生成断定(0),在可能声明未用无中泛型代码时,有用.一旦在这些例子时,就生成断定(0),然后结束程序.
在全局域初化无中式导致编译错误.

int a = throw new Exception("nope");

无法访问函数.
类似:

int a = () {
   throw new Exception(""); return int.init;}();
// 错误: 未抓编译时执行异常

enum也可noreturn,但必须显式初化为底层值,编译器,从0开始赋值.一直到溢出错误.

0长度noreturn静态数组,是单位类型.而[N]则表示N无中.
允许dmain(主)外(C)主返回无中.
表达式

二元表达式/参数左->右求值,如下:

int foo(int x, int y, string z);
int counter;

auto bar() {
   
    return foo(counter = 0, throw new Exception("left"), assert(0, "right"));
}

等价于:

noreturn bar() {
   
    counter = 0;
    throw new Exception("left");
}

||/&&/?:/赋值式仍然一样.

a || assert(0); //a为假,崩溃
a && assert(0); //a为真,崩溃
a ? b : assert(0); //a为假,崩溃
a ? assert(0) : b; //a为真,崩溃

int[] arr;
arr[assert(0, "left")] = assert(0, "right");
//定义断定为左,还是右.

转换操作符
显式转换任何类型无中.注意不要与隐式搞混了.它生成断定(0):

int x;
int bar();
auto foo() {
   
    cast(noreturn) (x = bar());
    //与{x = bar(); assert(0);}一样.
}

存储类

可用无中存储类声明中.
这些声明不生成存储,他们不影响生成代码,但仍受存储类的影响.因而,不能用无中修饰域/不变.

void foo(scope noreturn* ptr) {
   
    static noreturn* gPtr;
    immutable noreturn x;
    x = assert(0);//编译错误,不能赋值给不变
    gPtr = ptr; // 编译错误,转义针函数
}


不能从无中中继承.
尽管无中对象子类,但定义类型子类很难理解,增加复杂性,未来如果从无中继承有意义,则可能会放松限制.
当前,盖方法允许协变返回类型,但不允许用逆变参数类型.即,覆盖返回T的方法时,可用T子类.而参数时,必须用精确匹配类型.

class A {
   
    A foo(A param) {
   
        return param;
    }
}

class B {
   
    //允许返回B类型,但参数不行,因为返回可放松,而参数要精确.
    override B foo(Object param) {
   
        assert(0);
    }
}

添加底层类型不改变这些规则.可用无中T,因为无中所有T子型.但在参数上,则不行.必须精确匹配.

改变语法

抛语句变为抛式,这样可用在λ上.

NonEmptyStatementNoCaseNoDefault:
- ThrowStatement
- ThrowStatement:
-    throw Expression ;

UnaryExpression:
+ ThrowExpression
+ ThrowExpression:
+    throw Expression

限制

尽管由于隐式转换和无名字混杂可用无中声明c函数,c++函数指针的混杂中有中类型.同c++函数对接时,在有[[无中]]时,改中类型无中不一定有用.因为c++没有该类型,可按来在外(C++)中模拟无中.这对[[无中]]函数来说很常见.返回类型非时,可忽略[[无中]]或加包装代码:

extern(C++) int cppExit();
//中 整

void main() {
   
    writeln("hello world");
    cppExit();//不中,但无所谓
}

//可用dExit,其与类型系统交流不错
noreturn dExit() {
   
    cast(noreturn) cppExit();
}

//负载可加在调用者上
auto f = () => cast(noreturn) cppExit();

替代方案

@无中属性.类似:

version (LDC)
    enum noreturn = ldc.attributes.llvmAttr("noreturn"));
else version (GDC)
    enum noreturn = gcc.attribute.attribute("noreturn");
else version (DigitalMars)
    enum noreturn = pragma(noreturn);
//示例

// 用法:
@noreturn void exit();

然而,不能解决[]null,及λ中抛异常的问题,及将负担交给了程序员,而不是类型系统.

//无中类型
auto copyFunc(alias func, T...)(T params) {
   
    return func(params);
}

//无中属性
template copyFunc(alias func) {
   
    import std.traits: hasUDA;
    static if (hasUDA!(func, noreturn)) {
   
        @noreturn auto copyFunc(T...)(T params) {
   
            return func(params);
        }
    } else {
   
        auto copyFunc(T...)(T params) {
   
            return func(params);
        }
    }
}

破坏改变及过时

会破坏使用typeof([])的声明或模板函数.
也可能破坏用[]推导变量类型的代码.也可能与使用无中符号的模块冲突.

正式评估

d作者迅速通过该dip,并认为优于dip1017.


转载:https://blog.csdn.net/fqbqrr/article/details/113839120
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场