簡述:
這是一篇譯文,來自於Dartlang官方在Medium上的一篇文章,文章中說到Dart正在重新設計它的型別系統,並且即將要加入可空型別和非空型別(這一點和Kotlin語言是極其的相似,正因為這種可空和非空的型別系統的嚴格劃分,才能讓Kotlin很好地避免NPE的問題)。為什麼要翻譯一篇這樣的文章,算為我們下一篇語法篇Dart型別系統和泛型做一個鋪墊。因為當你進入Dart型別系統,你會發現它實際上是一個不嚴格的型別系統,比如泛型型變安全方面。
翻譯說明:
原標題: Dart nullability syntax decision: a?[b] or a?.[b]
原文地址: medium.com/dartlang/da…
原文作者: Kathy Walrath
Dart正在重新設計其型別系統,以便於各個型別都存在可空(該型別的表示式可以有null值)或者非空兩種型別。今年晚些時候,我們將告訴您具體的釋出時間和流程。但是Dart預設情況下都將不可為null, 並且當你需要為null的時候,必須使用特殊的語法來說明當前型別允許使用null值。
例如,當你要宣告一個整型可以為null,則需要在宣告的型別後面加一個?
int? someInt; // someInt變數可以為null
複製程式碼
如果你看過Kotlin、Swift或C#程式碼,可能對問號?
語法並不陌生。但是也存在一些不同的地方---特別是經常用來訪問集合和陣列中的下標([]
)運算子。C#和Swift都是使用?[]
,而目前Dart(以及ECMAScript)的計劃是使用?.[]
語法。
e1?.[e2] //如果e1為null,就返回null; 否則就返回e1[e2]的值
複製程式碼
這篇文章主要說明做這個定案背後的原因,並鼓勵你能有一些自己的想法和建議。這些內容大部分都是基於Bob Nystrom對issue #376的評論,該評論總結了Bob和NNBD規範負責人LeafPetersen之間的討論。
為什麼使用圓點語法方式?
e1?[e2]
與e1?.[e2]
兩種方式都有各自的優點。
e1?[e2]
:
- 與C#、Swift保持一致
- 更簡潔
- 類似於
!
運算子(即使它是可空型別,也可以使用它在表示式後面新增,表示它的值不為null)的語法:e1![e2]
e1.[e2]
:
- 類似於級聯語法:
e1..[e2] //級聯語法
e1?..[e2] // 可空檢查的級聯語法
複製程式碼
- 與其他可空檢查方法語法相似:
e1?.e2()
- 可以很自然地擴充套件到其他運演算法
- 可以避免以下程式碼中的歧義:
{ e1 ? [e2] : e3 }
我們花了一段時間嘗試找到避免?[]
歧義的方法。問題在於你無法判斷這這段程式碼{e1 ? [e2] : e3}
是否是一個包含條件表示式結果的set集合,或者是一個包含可空檢查下標key的map集合。
如果我們通過新增括號方式來消除歧義,可以選擇在整個表示式周圍新增--{ (e1 ? [e2] : e3) }
--可以明確地把它當做一個set集合。或者我們可以將括號包裹第一部分的周圍--{ (e1?[e2]) : e3) }
--可以明確把它當做一個map集合。但是在沒有括號的情況下,解析器就不知道該怎麼做了。
對於這種歧義有多種解決方案,但是似乎沒有一個令人滿意的解決方案。一種方法是依靠空格來區分選項。在這種方法中,因為這個空格在?
和[
之間,所以你總是會把e1 ? [e2]
作為條件表示式的前半部分。而且你總是將e1?[e2]
作為可空檢查的下標,因為這兩個標記之間沒有空格。但是依賴空格確實對開發者體驗不好。
從理論上來說,在格式化程式碼中,依靠空格不是問題。但是很多使用者將未格式化的Dart程式碼編寫作為格式化程式的輸入。因此,在該語言的每個地方,這樣輸入格式將變得對空格更加敏感且脆弱。到目前為止,在Dart中這類情況很少見,這是一個不錯的語法特性。(比如- - a
和--a
都是有效的但卻有著不同的含義
忽略歧義問題,使用圓點語法還有另一個原因: 如果我們為其他運算子(例如e1?.+(e2)
等)新增可檢測空值的形式,則可能需要使用點號。
我們為NNBD討論的另一項功能是可檢查null的呼叫語法。如果我們在此處不需要點,則它也存在相同的歧義問題:
var wat = { e1?(e2):e3 }; // Map or set?
複製程式碼
無論我們為?[
這種形式給出什麼解決方案,它都必須同樣適用於?(
這種形式。
最後,考慮鏈式呼叫下標的示例:
someJson?[recipes]?[2]?[ingredients]?[pepper]
複製程式碼
在我們看來,這看起來不太好。它看上去不像是方法鏈,而更像是中綴運算子的某種組合-有點像??
運算子。將其與以下程式碼進行比較:
someJson?.[recipes]?.[2]?.[ingredients]?.[pepper]
複製程式碼
在這裡,它顯然是一個方法呼叫鏈。視覺上看起來也很明顯,因為使用者需要快速瞭解有多少表示式會出現空短路。
總結這麼多,似乎看起來我們應該使用?.[
下面列舉一些原因:
- 它能避免歧義的問題。(詞法分析器已經將
?.
作為單個"空檢查"令牌) - 它能自然地擴充套件到可檢查空值的呼叫。
- 它能擴充套件到其他了解空值的運算子。
- 它讓Dart為格式化程式提供了更強大的輸入語言。
- 它在方法鏈中呼叫看起來符合習慣。
- 它從視覺上就能看出可以使多少個方法鏈短路。
你怎麼看?
我們一直對反饋和建議很重視,對於這種語法給出反饋最好方式就是對語言issue #376發表評論(或者給一些評論點贊)。那麼在使用的時候,請可以考慮檢視我們正在使用其他不錯的語言特性。
您可以在這裡找到更多資訊:
NNBD:
Dart語言的其他變更和特性:
譯者有話說
其實這篇文章主要就是討論一下,關於在Dart語言中對於陣列或集合下標可空語法的表達是應該用a?[b]
還是使用a?.[b]
. 然後作者給出一些為什麼會使用a?.[b]
的原因。通過這篇文章,我們應該還得到一個資訊就是Dart正在重構它的整個型別系統,它會像Kotlin那樣把型別系統分為可空型別和非空型別,從而實現使得整個型別系統更加完備和嚴謹。
Dart可空和非空型別已經處於實驗階段,但是在Dart2.x原始碼中已經在開始使用了。
比如dart中的ListQueue
的原始碼
//
class ListQueue<E> extends ListIterable<E> implements Queue<E> {
static const int _INITIAL_CAPACITY = 8;
List<E?> _table;//宣告List<E?>集合,泛型型別引數E為可空型別
int _head;
int _tail;
int _modificationCount = 0;
/// Create an empty queue.
///
/// If [initialCapacity] is given, prepare the queue for at least that many
/// elements.
ListQueue([int? initialCapacity])//宣告可空型別的int
: _head = 0,
_tail = 0,
_table = List<E?>(_calculateCapacity(initialCapacity));
static int _calculateCapacity(int? initialCapacity) {
//對可空型別initialCapacity做空檢查
if (initialCapacity == null || initialCapacity < _INITIAL_CAPACITY) {
return _INITIAL_CAPACITY;
} else if (!_isPowerOf2(initialCapacity)) {
return _nextPowerOf2(initialCapacity);
}
assert(_isPowerOf2(initialCapacity));
return initialCapacity;
}
...
}
複製程式碼
但是需要注意的是目前還沒有直接開放給開發者,還處於experiment階段。
所以,相信很快Dart中的可空和非空型別將會轉正正式使用,到那時候學過Kotlin、Swift或C#的開發者將會對Dart型別系統有更深的認識。有了這一篇的鋪墊,我們下一篇就可以正式進入Dart中的型別系統了,下一篇我們會將Dart中可選型別、泛型、協變、泛型型別具體化等。
我的公眾號
這裡有最新的Dart、Flutter、Kotlin相關文章以及優秀國外文章翻譯,歡迎關注~~~