考虑不变构:

不变 struct S { }

定义比如用​​Unqual​​​从​​类型​​​中去除​​不变​​​的​​模板特化​​.

mixin template Foo(T) { static assert(is(S == T)); }

mixin template Foo(T: 不变 U, U) {
pragma(msg, U.stringof);
//即使匹配,S应也是S,只有一个S.
static assert(is(S == U));
//错误::`is(不变(S)==S)`为假
}//T为不变的U
//`Foo!(不变(S), S)`从这里实例化.

应用至​​不变结构​​:

mixin Foo!S;

从​​2.076.1​​开始,它选​​第二个​​模板,且因​​静断​​而失败!设法取​​结构类型​​并去掉其固有​​常​​属性.

这很糟糕.如果按​​不变 构 S​​定义​​S​​,则不应取得​​非不变​​的​​S​​.

可能​​相同错误​​的​​不同​​实例:

不变 struct Struct { }

struct Wrapper {
void foo(U)(inout(U)) inout {
// 按'struct Struct'而不是'不变 struct Struct'推导U.
// 证据:
pragma(msg, isMutable!Struct.stringof ~ " - " ~ isMutable!U.stringof);
static assert(is(U == Struct));
}
}

Wrapper().foo(Struct());

按​​'struct Struct'​​而不是​​相应不变版​​推导​​U​​.

注意,即使从​​结构类型​​自身中​​去掉​​不变,它仍然​​应用至​​结构成员:

不变 struct S { int n; }
static if (is(S : 不变 U, U))
{
static assert(!is(U == 不变));
//U非不变,
static assert(is(typeof(U.n) == 不变));
//但U.n仍是不变.
}

我​​不认为​​结构有"固有​​常​​属性".规范说:

​这里​

​结构声明​​可有​​const,不变​​或者​​shared​​存储类.它与按​​const或不变或shared​​声明​​结构​​的​​每个成员​​效果相同.

所以应​​同等​​对待如下结构:

不变 struct S0 { int n; }
struct S1 { 不变 int n; }

现在请注意,即使对​​S1​​​,​​is(S1==不变(S1))​​​也不成立.​​真正​​问题是:

pragma(msg, S0); // 不变(S0)
pragma(msg, S1); // S1

​按名​​引用​​S0​​会产生​​不变(S0)​​类型,而​​不仅​​是具有​​不变​​成员的​​S0​​.

​1​​,​​2​​.

似乎是​​预期​​的行为.

仍然很糟糕.(及​​违反​​规范.),即语言无法区分​​不变 构 S {}​​与​​S {}​​的​​不变 S​​.

附加​​常​​到​​类型​​暗示了​​应该​​如何使用​​该类型​​.如,对返回​​Nullable​​的​​模板函数​​,我希望​​第一个​​返回​​Nullable!(不变S)​​,即​​Nullable!S​​,而​​第二个​​返回​​不变 Nullable!S​​.由于​​该错误​​,目前不行.

D的​​类型系统​​​目前​​保证​​​,对合格的​​Q(T)​​​类型,存在相应​​不合格​​​的​​T​​​类型.为此​​保证​​​引入​​特例​​​异常,可能会​​破坏​​​至少与​​修复​​​一样多的​​代码​​​(在​​Phobos​​​中,​​git grep 'Unqual!'​​来试试).

如果想向​​用户​​​提示默认,应用特定符操作​​类型​​​,更好方法(不考虑​​极端​​​)是,对​​私有​​​限定​​版本​​​用​​公开别名​​类型.

//lib.d
module lib;
private struct StructImpl { }
alias Struct = 不变(StructImpl);
//app.d
import lib;
Struct s;
static assert(is(typeof(s) == 不变));

:​​Q(T)​​…,

我​​不同意​​该说法,特别是​​因为​​有​​不变 构 S​​.​​Unqual​​表示它去除了​​类型​​限定符.但是​​不变 构​​并不是​​限定符​​,它只是"​​不变​​"声明.显然,目前是按​​隐式限定符​​实现的,这是​​该错误​​意义所在,不符合​​规范​​.

有​​不合格​​​类型有什么意义?它与​​类型​​​的​​可变​​​转换值不同.(那需要​​头可变​​​.)因此,不能造"可变​​T字段​​",这无意义.

D未提出要求,​​Paul​​提出了要求.它按如​​修饰名​​的​​修改​​类型来​​定义不变​​,​​这里​​:

编译器用它​​标识​​类型.另见按顶级​​Type​​类中字段​​实现​​限定​​类型符​​,​​这里​​:

想发明新的带​​自身混杂​​​的"​​不变构​​​"概念?因为我不喜欢复杂化​​类型系统​​​,所以正在​​寻找​​无特例方法.

​不变属性规范​

它说​​不变​​与​​常​​一样,所以看一下​​常​​部分,​​这里​​:

​const​​属性​​从T​​更改​​声明符号类型​​到​​const(T)​​,T为无​​常​​时为​​引入​​符号​​指定​​(​​或推导​​)的类型.

​本段​​​隐含假定​​声明​​​符号​​具有​​​类型.但是在​​不变 构 S​​​中,就,​​S​​​没有​​类型​​​,它​​*是*​​​类型.因此,规范并没有​​明确​​​回答"​​不变构S​​"的意思.

问题是​​Unqual​​似乎有​​相互矛盾​​的目的,这最终是关于​​Unqual​​的,关于​​取类型​​,并取​​可变版本​​的能力.

问题是,一方面,​​不变​​表示"​​每个字段​​都标记为​​不变​​".但标记​​字段​​为​​不变​​废弃了​​Unqual​​概念,因为声明​​Unqual!T​​,将不再是​​可变​​值.

好的,因此认为只是有,​​可变​​和​​不变​​类型,及是一般无用的​​Unqual​​特殊工具.但如何从​​类型​​中​​去掉不变​​?

因为现在必须区分"按​​不变​​标记类型"和"按​​不变​​标记的​​类型声明​​".因为,既然有不能转为​​可变​​的​​类型声明​​.则不能从​​不变 构 S​​等去除​​不变​​.

因此,我要区分​​'不变 构 S'​​和​​'构 S,不变 S'​​,因为:

1,用户想有​​普通 S​​,尤其是在​​构造​​类型,按​​内部​​字段用时;即,就像​​不变 可无效!S​​,尽量扩大​​不变​​.

2,用户断定永远不处理​​可变 S​​.

这是​​对​​​的,因为这给了带​​管用​​​不变量的​​纯值​​​类型.​​域​​​类型,不必在​​访问器​​​后面​​隐藏​​​每个字段,因为​​*只能*​​​通过构造器​​设置​​​字段.因为适合​​域类型​​​.这就是​​不变 构 S​​的用途.

正如​​丹尼斯​​​所说:​​减少​​​类型系统中的​​特例​​.

忘记​​Unqual​​​.它只是​​示例​​​(显然不好).​​避免类型系统​​​中的​​特例​​是重点.

考虑:

class PassengerWagon;
class CargoWagon;

struct Wagon {
PassengerWagon passengerWagon;
CargoWagon cargoWagon;
}

​近似​​求和类型,但问题是,如,​​货车​​既可以是​​乘客​​也可以是​​货车​​.而显然​​货车​​不能这样.这是​​建模​​错误.

用​​不变量​​来解决它:

struct Wagon {
PassengerWagon passengerWagon;
CargoWagon cargoWagon;
invariant((passengerWagon is null) != (cargoWagon is null));
}

现在当然这是​​非常危险​​类型!因为如果你

auto wagon = Wagon(passengerWagon, null);
wagon.passengerWagon = null;

无声地​​违反​​了​​不变量​​,因而是​​漏洞​​之源.此外,​​隐式​​结构​​构造器​​不检查​​不变量​​,所以可做​​一些​​愤怒的事情,比如​​Wagon()​​.

所以加强它!

struct Wagon {
private PassengerWagon passengerWagon_;
private CargoWagon cargoWagon_;
invariant((passengerWagon_ is null) != (cargoWagon_ is null));
@disable this();
this(PassengerWagon passengerWagon, CargoWagon cargoWagon) {
this.passengerWagon_ = passengerWagon;
this.cargoWagon_ = cargoWagon;
}
PassengerWagon passengerWagon() {
return passengerWagon_;
}
CargoWagon cargoWagon() {
return cargoWagon_;
}
}

注意到,​​该简单类型​​开始​​烦人​​.即,可怕.​​可读性​​差,​​写起来​​糟糕,且很容易出现​​错别字​​.

我们在很多领域有​​很多​​这样​​结构​​.太浪费时间.

我引入了​​样板​​来修复它:

struct Wagon {
@ConstRead
private PassengerWagon passengerWagon_;
@ConstRead
private CargoWagon cargoWagon_;
invariant((passengerWagon_ is null) != (cargoWagon_ is null));
// 里面,仍然生成构造器.
mixin(GenerateThis);
mixin(GenerateFieldAccessors);
}

现在它更短了,但仍然​​有点烦人​​​,而且,当​​构建​​​时,机器都会​​耗尽​​​内存.样板文件中的​​数千行​​​模板与此​​有关​​吗?可能永远不会知道.

但是​​真正想要​​的不就是​​强制​​所有​​字段​​都只能用​​构造器​​设置吗?

不是已经有了​​确保字段​​不会​​改变​​的​​D语言​​功能吗?

immutable struct Wagon {
PassengerWagon passengerWagon;
CargoWagon cargoWagon;
invariant((passengerWagon_ is null) != (cargoWagon_ is null));
mixin(GenerateThis);
}

​漂亮而简单​​,没有​​访问器​​,基本上没有​​模板​​,没有​​绕过​​不变量的​​意外改变​​.

当然,有​​不变​​的大量​​错误列表​​.

顺便,有内部实现​​哈希映射​​!不是为了性能,只是为了处理​​不变​​类型.

就是那样的​​简单结构​​,带简单​​注解​​,​​零​​模板开销,太诱人了,​​不变​​的纯数据.它应该是编写​​安全,可信赖​​代码的​​关键组成部分​​.

如前,​​不变​​构就足够了.​​类型​​虽然去掉了​​不变​​,但内部成员,没有去掉​​不变​​.

为了区分,​​不变 构 S​​与​​构 S{不变:}​​,你可能引入新​​特例​​.我错过了什么吗?

这管用吗?

struct S {
immutable:
// 字段...
}

既然如此,就应该让​​不变 构 S==构 S{不变:}​​.