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
行推导出来的.”
显然,这仅适合具有推导属性
的函数;如果属性
在代码中是显式
的,那么就无可说的了.由于具有推导
属性的函数必须始终
可访问其主体
(否则无法推导),因此可以保证
始终可以找到上述引用
.
编译器
确实应该给这些信息,而不是让自己弄清楚
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现