0
点赞
收藏
分享

微信扫一扫

d区间函数属性实践


​​原文​​

import std.algorithm;

struct Foo(R) {
R r;
int i;

bool empty() @nogc nothrow pure @safe scope {
return r.empty;
}

auto front() @nogc nothrow pure @safe scope {
return r.front;
}

auto popFront() @nogc nothrow pure @safe scope {
r.popFront();
}
}
//Foo尽量加属性.但不能为常,因为`popFront`是可变的.
auto foo(R)(R r) {
return Foo!R(r);
}

int count;

void main() {
[ 1, 2 ]
.map!((i) {
++count; // <-- 不纯
return i;
})
.foo;
}

​r.front​​​是不纯的,​​λ​​​是​​@nogc​​​也会有类似编译错误.
因为​​​Foo​​​是模板,它不应在​​成员​​​函数上放​​属性​​​?还是仅​​使用依赖模板参数成员​​​的成员函数?是模板的​​非成员​​​?
与​​​不纯​​​工作不好,其余好的.
是否放​​​函数属性​​​在​​单元测试块​​​上来抓​​此类问题​​?

@nogc nothrow pure @safe
unittest
{
// ...
}

是的.除了​​@trusted​​​,​​模板​​​代码上的​​显式​​​属性有问题.
要测试​​​特定代码​​​是否是​​纯​​的,这样:

static assert(!__traits(compiles, () pure {
// 代码
});

​pure,@nogc,nothrow​​​,等属性​​@safe​​​都应该留待​​推理​​​.函数要么可​​执行​​​这些属性,要么不能.
​​​const​​​或​​inout​​​是不同的属性,这些不是​​推导​​​的,你要​​自省​​​来确定.这真的很不幸,因为没有​​简单​​​方法可以"如果允许,则为​​const"​​​,你必须​​重复​​实现.

这并不可怕,因为我不确定​​传递​​​不纯函数给​​模板​​​会怎样.
你不必​​​测试​​​编译器推导,只要期望它可工作.你要​​测试​​​的是,你为​​Foo​​​编写的代码是否使​​应该​​​纯的东西​​不纯​​​.
因此,如,创建纯​​​nogc,nothrow​​​,安全的​​虚区间​​​,并按该区间的​​包装器​​​测试​​Foo​​​,给​​unittest​​​加上属性.如果管用,应该很好.​​不应​​​测试如果传入​​不纯​​​函数,就会​​不纯​​​.
如果有基于不同​​​属性​​​而​​编译​​​的代码,你也应该都​​测试​​​,确保​​覆盖​​相关代码.

我认为,应该​​省略​​​模板​​聚集成员​​​的属性,​​*除非*​​​你想在​​所有实例​​​上确保​​该属性​​​.如,如果​​语义​​​要求,​​某方法​​​不应改变​​状态​​​,那么可在其上放​​const​​​.
否则,我会让​​​编译器​​​推导​​实际属性​​​,并在​​模板代码​​​中用​​适当制作​​​的​​单元测试​​​来抓​​属性违规​​​.这样来​​最大化​​​一般性:
​​​1​​​,如果用户想用你代码,并与他们自己的​​数据类型​​​一起,但需要​​不纯或抛​​​,那么你模板应该"​​优雅地降级​​​"而不是​​拒绝编译​​​(因为用要抛的​​.front​​​实例化​​Foo​​​编译会是错误).为此,必须让​​编译器​​​尽量多地​​推导​​​属性.如,对不抛​​.front​​​的实例化,它会推导出​​不抛​​​.但是对涉及​​抛​​​的用户类型的​​实例化​​​,编译器会推导​​.front​​​为​​抛​​​.
​​​2​​​)如果用户在​​不抛​​​代码中使用你代码,那么​​模板​​​不应引入​​无法编译​​​的​​抛​​​.为此,应用​​适当​​​属性的​​单元测试​​​来确保,如,当​​Foo​​​用​​非抛​​​类型实例化时,它不会引入​​抛​​​.
你当然可以.​​​纯 单元测试​​​代码显然​​自身​​​必须是纯的(否则​​不编译​​​).如果​​Foo​​​引入了​​不纯​​​,那么作为​​纯​​​的单元测试禁止调用​​Foo​​​的​​不纯​​方法,这正是想要的.有什么问题?

这很简单:编写​​单元测试​​​,用故意不纯的类型实例化​​Foo​​​(如,​​.front​​​引用聚集​​外部单元测试​​​中的一些局部变量).如果​​Foo​​​中有免费的​​pure​​,这不会编译.

你思考方式错了.
使用​​​模板​​​函数时加上​​pure​​​,你是在说"仅允许可为​​纯的函数​​​实例化".本质上,就是告诉用户"它必须是​​纯​​的!".

如果意图是仅强制​​纯函数​​​,就是你这样.如果意图是​​确保​​​给定​​正确参数​​​,​​函数​​​将是​​纯​​​的,那么答案是​​单元测试​​​.
有时这真的很烦人.就像​​​单元测试​​​失败一样,你得到的为什么​​不工作​​​的信息​​很少​​​.
即,期望​​​推导​​​是​​纯​​​的,但不是.你得到的只是"​​不纯​​​的单元测试不能调用​​不纯​​​的​​foo(...)"​​​函数.今天​​很难​​​找出​​错误推导​​原因.我希望它会更容易.

​+1​​​,需要更好的​​诊断​​​.
这样?:每当​​​编译器​​​推导某个​​F的函数​​​属性时,连同推导属性,它还会附加​​排除​​​属性在推导之外的​​位置列表​​​.如,如果推导​​F​​​为​​不纯​​​,编译器还保存F中​​不纯​​​操作的第一行位置.每当​​纯代码​​​调用F时,编译器打印出此引用(文件+行+列)及错误消息.即,“G纯函数不能调用​​F​​​不纯函数,因为​​不纯​​​是从F函数中的第​​1234​​​行推导出来的.”
显然,这仅适合具有​​​推导属性​​​的函数;如果​​属性​​​在代码中是​​显式​​​的,那么就无可说的了.由于具有​​推导​​​属性的函数必须​​始终​​​可访问​​其主体​​​(否则无法推导),因此可以​​保证​​​始终可以找到​​上述引用​​​.
​​​编译器​​确实应该给这些信息,而不是让自己弄清楚


举报

相关推荐

0 条评论