[譯] Dart中可空性語法的定案: a?[b] 或 a?.[b]

mikyou發表於2019-11-17

簡述:

這是一篇譯文,來自於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]

譯者有話說

其實這篇文章主要就是討論一下,關於在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中可空性語法的定案: a?[b] 或 a?.[b]

所以,相信很快Dart中的可空和非空型別將會轉正正式使用,到那時候學過Kotlin、Swift或C#的開發者將會對Dart型別系統有更深的認識。有了這一篇的鋪墊,我們下一篇就可以正式進入Dart中的型別系統了,下一篇我們會將Dart中可選型別、泛型、協變、泛型型別具體化等。

我的公眾號

這裡有最新的Dart、Flutter、Kotlin相關文章以及優秀國外文章翻譯,歡迎關注~~~

[譯] Dart中可空性語法的定案: a?[b] 或 a?.[b]

Dart系列文章,歡迎檢視:

相關文章