原文 許多人要求更多
自動推導
屬性.
推導屬性
今天
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
綁定.
是以,雖然在
特例
下會
加倍
,但
庫作者
是最期望的,今天修複它并不太難,以後可能會
更容易
.