d玩转不变
领域驱动设计
把值对象
视为不变
的.使无副作用
函数,不依赖可变状态
.
域事件一般不变
,不应修改
域代码操作
的数据,相反,总是返回新数据
.
大量使用UFCS
域链来限制使用内存
.
最好静态保证!
想用不变
,传统上:
struct ArrayContainer
{
@ConstRead
private int[] array_;
...
mixin(GenerateFieldAccessors);
}
但,基于模板:构建慢,内存使用率高
.脆弱
,对编译器更改敏感
,不可传递:需要array_
的潜在副本
想要:
immutable struct ArrayContainer
{
int[] array;
}
但不能用不变
!
● 想使用不变
数据类型.
● 想使用区间
.
● 还使用了很多关联数组
.
● 但是不变
数据类型打破了许多区间
,并且不适合关联数组
!
immutable struct S { int i; }
...
auto list = [S(2), S(4), S(3)];
错误,不能修改...
.不能用不变
.
immutable struct S { int i; }
...
auto list = [S(2), S(4), S(3)];
assert(list.maxElement!"a.i"== S(4);
//错误!无法修改.
为何?如下,很明显应工作:
T fun(T)(T arg1, T arg2) {
T result = arg1;
result = arg2;
return result;
}
不能用Unqual
!
T fun(T)(T foo, T bar) { Unqual!T result = foo; result = bar; return result; }
Unqual
,只是去掉最外层
的限定符
.字段,仍然是不变
的.
immutable struct S { int i; }
void main() {
Unqual!S s;
static assert(!is(typeof(s) == S));
s = S(5);
}
//错误!
如果覆盖不变,会怎样?
immutable struct S {
int field;
}
void genericFun(T)(T first, T second) {
auto store = first;
immutable int* ptr = &store.field;
int firstField = *ptr;
store = second; // 危险!
int secondField = *ptr;
// 失败!
assert(firstField == secondField);
// 观察到了不变指针改变它的值,全部丢失,等等
}
问题,是不能修改不变
字段.观察
不变引用
,改变了不变
的值
.用头可变
来放松
常系统?
struct Turducken(T) {
Turkey store;
struct Turkey {
Duck duck;
}
union Duck {
Chicken chicken;
}
alias Chicken = T;//`T`封装在`联`中的`构`中
//即使T有不变成员.
}
因为是联
,所以不调用析构器
.
因为是结构体
,所以,即使T有不变
商店,也可用std.algorithm.mutation.moveEmplace()
.
可以控制生命期.故意的.
缺点是,未来可能会改变
.
如何写头可变
align(T.alignof)
struct HeadMut(T) {
void[T.sizeof] data;
}
精确扫描
的GC
问题:始终按指针对待void[n]
.
想要DeepUnqual
.用DeepUnqual
来帮助.
rebindable.DeepUnqual
,为每个D数据类型
定义等效类型.可变
指针在一堆.不变
指针在另一堆
.其他,都不一样.
DeepUnqual!T
精确GC
,扫描扫描T的字段
.相同大小,相同对齐(中性
),同它工作,是深度不安全
!你要转换
一切.
置(T)==>重绑定(T)(神秘)==>T 取()
.
如何保存
T,如何取T
?
Rebindable value;
value.set(S(5));
assert(value.get == S(5));
//即使S为不变.
但,安全
吗?
是秘密
吗?安全
吗?
● 只要T是秘密
的,它就是安全
的.
● D不变
混合了内存不变性
和可观察性
.
● 只要未观察
到变化.就不在乎
值是否变化.
● Rebindable!T
是包装T
.
● 不能按T形式
取包含数据
的引用
,因为get
按值返回.
● 由于DeepUnqual
,存储的数据
是可变
的,因此可以改变Rebindable!T
.从不改变未声明
为可变
的内存
.
● 并且内存
自身未按不变
公开,而是返回不变
的值副本
.
虽然数据
存储在Rebindable
中,但它是可变化
的,但每次变化
都必须是从有效
状态到有效
状态,无法在变化过程
中抓到它
.
● 弱点:T
不能依赖由构造函数
或者复制构造
函数创建的每个地址
.
● 如果复制构造函数
到处搞乱字段地址
,就会中断
.
还有:Nullable, rebindable.Nullable
工具.
Nullable!(const int) ni;
assert(ni.isNull);
ni = 5;
assert(!ni.isNull && ni.get == 5);
ni.nullify;
assert(ni.isNull);
不变安全
的关联数组,rebindable.AssocArray
AssocArray!(int, S) assocArray;
assocArray[0] = S([5]);
assocArray[0] = S([6]);
assert(assocArray[0] == S([6]));
建议:可引用性
是万恶之源
.
● immutable
是不是太强大
了?应该可覆盖不变
字段吗?
● 否:不变
太弱了!
● 问题是可观察
到不变字段的变化
.
● 默认,可&i
并取得i
的永久视图:&i
是immutable(int)*
,但可能改变值!
● 什么比不变
更强大?右值
!
右值
:只能出现在右边
的,就叫右值
.
假设右值结构{}
可比不变
结构更强大,但仍可通过分配新值
来覆盖!
右值构
是纯数据结构:不取字段地址
,不引用
字段,不直接分配
字段.
为什么?这样,就不必害怕突变
,因为没有人能抓住
我们.
● 如果只赋值新构建
的T值
,则不会破坏不变性
.
● 纯右值
比不变
更强:它是不可引用
的,因此是不可观察
的.
● 事实上,它没有内存
:它仅是数据
,不保存在内存
中.
● 可读取它的值
,但不能远程观察
它的字段,因为它是旧数据
.
实际可行
建议:彻底删除
不变结构字段
.
为什么Unqual!T
不起作用?为什么Unqual!T
不应工作?
标准库
假定Unqual!T
造头可变
.
结构内部
可隐藏不变
字段,创建独立
的不变补丁
.
immutable struct S
{
//实际上,仅能通过`不变本`访问immutable(int)[]
int[] a;
}
Unqual
已为构外
类型创建头可变
值.
字段
实际上总是头可变
的,immutable(T)
仅为T var
设置默认值.
实际上,不变
使每个字段为Unqual!(immutable T)
.
直接
访问可变(T.field)
字段,先隐式转为不变
,如果不能,则是错误.
这保留了常
,保留了不变
,不需要
访问器.
但允许在数据结构中,通过Unqual!T
使用任何T
.效果:Unqual!T==HeadMutable!T
,总是.但未完全解决类的头可变
问题.(immutable(Object)
不会隐式
转换到Object
.).但是在域代码
中,不会使用类.:-)
无论如何,存在std.typecons.Rebindable
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现