0
点赞
收藏
分享

微信扫一扫

d推导属性的想法


​​原文​​​ 许多人要求更多​​自动推导​​属性.

推导属性

今天​​D​​​为​​auto​​​返回及​​模板​​推导属性:

auto foo() { }

void bar() {}

pragma(msg, typeof(foo)); // pure nothrow @nogc @safe void()
pragma(msg, typeof(bar)); // void()

注意,不用写,​​foo​​​自动得到​​pure nothrow @nogc @safe​​​,这就是​​推导属性​​​.
​​​rikki cattermole​​​提议扩展它,来方便关注​​属性​​的用户.我喜欢,但要注意:

​运行时​​​调度
函数文档
​​​兼容​​​合同
​​​依参​​​属性
​​​ABI​​​和​​.di​​文件兼容性

运行时调度

先要知道这些是​​静态属性​​​,除非强制,与​​运行时调度​​不一致.

interface Foo {
void func();
}

class Bar : Foo {
void func() {}
}

示例中,显然无法推导​​Foo.func​​​,因为没有可分析主体.可继承它并干活;​​接口​​​不做保证(当然,除非接口明确指定了​​一些属性​​​,迫使它的​​所有实现​​​至少也如此严格).
但是​​​Bar.func​​​确实有一个主体.可推导出来吗?好吧,如果它是​​final​​​,我会说​​是的​​​:在​​子类​​​实现上使用比​​父接口​​​要求更严格的​​属性​​​.所以与父接口的关系不是障碍;可按需​​严格​​的推导,并且仍可实现.

但是,由于​​Bar.func​​​不是​​final​​​,因此​​调用​​​它时,并不知道​​实际​​调用的函数体.考虑:

class Baz : Bar {
override void func() { some_global ~= x; }
}

void main() {
Bar b = new Baz();
b.func();
}

此时,即使是选择调用的​​Bar.func​​​接口可能已经可使用它,​​主函数​​​也不能是​​@nogc​​​或​​pure​​​.因为​​override​​​导致运行时替换​​函数体​​.

教训是:可推导​​终(final)方法​​​属性,但不能​​推导​​​虚方法,因为​​运行时​​​调度允许实现的​​正式接口​​​替换主体.必须用​​显式属性​​​限制​​子类​​​,否则会破坏​​替换​​.

除非标记​​Baz.func​​​为​​final​​​并确保通过该接口调用它,否则​​推导​​​失败会传播到​​main​​:

class Baz : Bar {
final override void func() { some_global ~= x; }
}

void main() @safe {
//这很好,仍可在`Baz`上推导出`@safe`,因为它是`final`,因此知道,实际使用的函数体.
Baz b = new Baz();
b.func();
}

函数文档

​自动生成​​​文档倾向于查看​​静态注解​​​,但了解​​推导出的内容​​​可能很有用.​​docgen​​​希望编译器告诉​​docgen​​​推导内容,但是​​docgen​​​应该指出它是​​推导​​​出来的.因为​​兼容性合同​​,这很重要.

兼容性合同

如果库在​​未来版本​​​中推导出的​​属性​​​变化了,这是重大更改吗?好吧,(就像定义​​重大更改​​​一样),我的回答是"得根据你的​​承诺​​​".文档定义了这些​​承诺​​​,因此​​文档​​​要指出​​静态保证​​​和​​推导​​间的区别.

如果我写:

Color parseColor(string name) @safe @nogc { ... }

则我保证它是​​@safe​​​和​​@nogc​​​.如,如果要发布有​​分配​​​的​​新版本​​​,则是​​重大变化​​​.
但是,同时,很可能推导它为​​​pure​​​,然后就可在​​纯函数​​​中使用它.它会​​编译​​​,但由于我列举​​pure​​​,我不保证未来保持​​纯​​​.
当然,很有可能!我可能在​​​未来​​​版本中添加​​pure​​​来扩展保证.但是,如果将来更改为,如,从​​全局变量​​​中检查用户​​本地​​​,则推导不再有​​pure​​​.而且由于我没有列举​​纯​​​,我可叫它​​新功能​​​(次要版本加1),而不是​​破坏性更改​​(主要版本加1).

这样就​​很好​​.

依参属性

因为​​实现​​​而改变的​​推导属性​​​是一回事,但如果它因传递​​不同参数​​​而改变呢?
这在今天适合模板,但​​​代价​​​是另一个​​实例化​​:

void forward(Dg)(Dg dg) {
dg();
}

void main() {
void delegate() notSafe;
void delegate() @safe yesSafe;

//调用`forward`时,自动用参数类型实例化它.
//所以调用`forward(notSafe);`它会这样:
pragma(msg, typeof(forward!(typeof(notSafe))));
//注意用`notSafe`创建时是,`system void(void delegate()dg)`
//而调用`forward(yesSafe);`它这样:
pragma(msg, typeof(forward!(typeof(yesSafe))));
//但当用`yesSafe`创建时,它是`@safe void(void delegate()@safe dg)`
//注意一个是`@system`,另一个是`@safe`.
//它从参数中推导出不同的类型.
//但请注意它是不同的类型和不同的地址:
assert(&forward!(typeof(notSafe)) != &forward!(typeof(yesSafe)));
//因为它们是不同的实例化!
}

模板干的是把​​给定参数​​​粘贴到​​新函数体(a)​​​中,然后通过​​推导属性系统​​​传递​​a​​​.
常见模式是使用​​​带注解​​​的​​单元测试​​​来说明,它有时可能有​​该属性​​​,测试提供了一例,这样编译器在​​编译​​测试时检查它.

但是,如果推导​​非模板函数体​​​,则只有​​一个主体​​​,因此要推导​​参数​​​,必须使​​该部分​​​成为单个类型,而不是依赖传入的​​多个​​类型.

有一个​​依参属性dip​​​,该概念简称为​​inout属性​​​,该想法使​​函数​​​的属性依赖于​​参数​​​属性.使函数​​其余​​​部分比​​属性​​​要求更严格.但推迟最终​​结果​​​到​​传递内容​​​.只需要​​一个函数体​​​就可完成,不必​​一个函数,多个副本​​.

ABI和.di文件兼容性

​d​​​作者担心,​​属性​​​成为​​混杂名​​​的一部分,如果实现​​调整​​​更改了​​推导​​​属性,也会​​更改​​​链接器看到名字.如果​​绑定​​​不匹配,会导致​​"未定义引用"​​错误.

它放在最后,因为这是我最不担心的;​​沃尔特​​​担心它.​​更改代码​​​可能会​​破坏​​​接口,并不奇怪,但它与​​更改函数体​​​,并改名和改原型或改链接没啥区别.
可这样缓解:​​​1)​​​D中单独​​绑定​​​相对较少,最好​​简单​​​使用​​源码​​​,及​​2)​​​可用​​dmd-H​​​自动生成它们.现在,​​dmd-H​​​很糟糕(我想修复它,编译器应该可使它更好工作,另外生成​​.di​​​或可能包含​​推导​​​值的​​dmdjson​​​输出可能是​​docgen​​​的​​良好数据源​​),有时它确实有效.

而且当你​​遇见​​​链接器错误时,很容易​​更新​​​绑定,尽管回到​​兼容性合同​​​概念时,需要​​更新绑定​​​可能会让人感到意外.但是,库作者应该提供​​.di​​绑定.

因此,虽然在​​特例​​​下会​​加倍​​​,但​​库作者​​​是最期望的,今天修复它并不太难,以后可能会​​更容易​​.

编译速度

额外问题:​​推导属性​​​会增加编译速度吗?我怀疑这是否重要,但如果确实如此,​​.di​​​生成都可返回并​​缓存​​​结果.
我希望正式绑定​​​.di​​​和​​.obj​​​文件,且很好支持它.如果​​函数​​​在目标文件中,则生成​​匹配​​​声明,这样,编译器​​知道​​可重用和不可重用的内容.

结论

现在可​​有效​​​扩展​​推导属性​​​.可​​推导​​​编译时已知函数体的​​函数​​​的属性,(基本上​​非虚​​​的,且非​​无函数体​​​声明),应工作得很好,并且在不给​​库作者​​​带来负担的同时,​​扩展​​​了这些​​属性​​​的​​可用性​​.


举报

相关推荐

0 条评论