總數間隔格式問題 |
|
整個爭論點的根源在於程式設計的歷史。C 等一些語言的實作細節是,陣列項目透過指標 + 位址偏移量來參照。因此,偏移量存在於實作層級,這導致語言設計者必須選擇將此架構轉移到語言本身,使程式設計師必須適應它,或讓編譯器或解釋器從所有索引減 1,以便(通常為人類)程式設計師可以使用序數。C 沒有選擇這樣做,因此 C/C++/Java/Python/Ruby 整個系列語言家族也沒這樣做。由於 C 非常成功,希望他們的「寶貝」被廣泛使用的語言設計者必須考慮這個事實上的標準,以及除了他們也是 C 程式設計師這項事實。現在,宣稱一個功能是「自然的」,因為它是一個標準,這僅僅是邏輯錯誤。
陣列、串列、序列和表格都是有序的集合。人類使用序數--第一、第二、第三(或 #1、#2、#3)--來指出集合中的一個元素(哪一個?)。相反地,他們使用基數--1、2、3--來計算元素的數量(多少?)。不幸的是,程式設計大多以英語為主,而且該語言會將這兩種數字類型都稱為「數字」(與德文的 Nummer/Zahl 或法文的 num�ro/nombre 相比)。儘管如此,所有語言對於這兩者之間都會做出重大的區別。人類生活的各個領域中,除了程式設計的一個子領域之外,序數都會用於指出事物。尋找第 0 年、書架上的第 0 本書,或貝克街 0 號 ;-)。此外,序數似乎最先出現,這也是為什麼「零」這個基數在人類歷史中如此年輕的原因。語言見證了這個事實:人們會說「沒人」、「從未」和「沒什麼」,而不是使用「零」這個詞。所有這些的目的是說,在人類層面上,對程式設計師友善的選擇是使用序數,而不是偏移量,來參考有序集合中的項目。
a[(i - 1)%n + 1]
。一天的第一小時是 12,而最後一小時是 11(看起來他們心思縝密地讓一點從午夜後一小時開始,但當午夜本身到來時卻亂了套)。十人份的清單總是有一組兩位數字;這徹底搞混了孩子們學習從 1 數到 10 以及閱讀到 10,當他們必須學習零時,更是让他们混亂不已。一趟行程的第一公里從 0 公里開始。 �MarceloCantos?
類似地,閉區間恰巧更自然或直觀。「從第一個到第三個」在任何情況下都是一個包含式的含義。因此,使用半開區間首先需要心智運作,最後才會自動化。這既不表示這種語法較沒效率或較不合理,僅是對於非入門者而言較不人性化。有些程式設計師似乎對於這類導致新手上路的錯誤的深奧功能感到自豪 ;-)。
這裡有必要區分程序和人類語意層次。在程序層次上,這兩個方案同樣適用,而且語意都相符。在人類層次上,0 起始點/半開範圍方案需要從程序層進行一種「轉碼」。因此,如果某人同意程式碼(例如數學表達式)會先由人類而非機器讀取,那麼這個方案會是不佳的選擇——只有在有理由證明替代方案不可能時,才應選擇這個方案。請注意,與 C 一樣古老的 Pascal 採取的是相反的方式——這並非巧合,因為它的設計適合用於教育。
在日常工作中,一旦習慣了其中一種慣例,兩種方案都證明可以使用。當程式設計師討論這個問題時,他們往往會堅持個人的舒適習慣,也就是每天使用所建立的心智習慣。由於大多數使用的語言遵循 C 的慣例,因此有許多論點支持它。現在,仔細觀察後,這些話恰好錯了,因為它們並非合乎邏輯的理由:它們是理性的表達背後的意見。
例如,關於以 [n,n)
最佳表達的 0 長度範圍的論點或多或少是沒有意義的;表達空序列的唯一明智方式是 []
。[5,5)
和 [5,4]
只是在語義上荒謬。現在,它可能發生在執行期間,序列切片是空的——這是另一個層次:程式設計師不會在設計期間處理它。
[]
這種方式來表示空的範圍很尷尬。電腦程式通常會使用具備兩個端點的資料結構:struct range { int start, end; };
。兩個此類範圍的相交守則非常簡單:range intersect(range a, range b) { range r = { max(a.start, b.start); min(a.end, b.end); if (r.end < r.start) r.end = r.start; return r;
},基數也很簡單:int count(range r) { return r.end - r.start;
},空的函數也很簡單:bool empty(range r) { return r.start == r.end;
}。只要您需要為空的範圍使用不同的表示法,所有事項都會突然變得複雜,並耗盡更多 RAM 和 CPU。您還會發現自己非得在程式碼中廣泛地灑上 + 1
和 - 1
,以修正各種因為一而十的問題(例如,現在計算需要 r.end - r.start + 1
,以及明確測試空狀態)。 �MarceloCantos?
end - start
,而不管該區間是否為連續或離散,而「閉區間」的「長度」公式對於連續和離散區間來說是不同的。作為一個實際且很常見的範例,半開日期區間中的天數計算方式是一樣的,而不管終點是日期還是時間戳記。這也是我比較偏好date1 <= d AND d < date2
而非 SQL 的BETWEEN
運算子的原因;我不用思考有問題的變數是日期還是時間戳記,或擔心維護人員是否會將其中一個類型切換到另一個類型。 �MarceloCantos?
[5,5)
是一個退化的情況,而此類案例通常很有用。究竟是什麼讓它荒謬? �MarceloCantos?
在 21 世紀,這樣的時間和精力損失是否仍會發生?至少有兩種方法可以解決此問題。
正如某些註解,BASIC 允許明確表達陣列索引配置。例如,array(0,10)
會定義一個從 0 開始的索引範圍。從類似的觀點來看,可以使用任一數學通用語法明確使用半開區間: [a,b)
或 [a,b[
。此選項有額外的優點,可以消除容易出錯的格式,因為所使用的配置已明確寫入。這些對人類讀者來說是很好的,但問題仍然是正確解讀未習慣的慣例所寫的表達式。以下提案解決了這個問題。
作為程式設計師,我們都很熟悉良好的編輯器自訂化功能,例如縮排選項,讓我們可以選擇是使用標籤還是空格,或縮排寬度應為何者。請注意,這可以用於讀取/載入或編輯/儲存原始碼檔案。無論作者選項為何,我們都可以使用自己偏好的慣例來讀取和編輯程式碼。無論我們的選項為何,另一位開發人員都將能夠使用自己的選擇來讀取和編輯相同的程式碼。現在,檔案可以依據任何標準規範儲存,無關緊要。這是一種透過編輯器自訂化層級執行的前景/背景區分。太棒了!
現在,何不將此原則延伸到*任何*語言功能?例如,有人可能會希望擺脫賦值用的「=
」(這在語意上是錯誤的),將它替換為例如「:
」,並只將「=
」用於邏輯等式。載入檔案時,編輯器應使用這些偏好顯示程式碼,而不管儲存時所使用的標準為何。
索引和切片也是一樣:C 程式設計師會設定 0 為起始點的半開切片,並以這種方式取得顯示的程式碼,而不管儲存時的背景標準為何。習慣使用 Delphi 的程式設計師會選擇相反的配置,無論如何。
[你對此有何看法?我現在主要使用 Python 程式設計,而大多數針對這類語言的 IDE 也都是用 Python/wxPython 編寫的。因此,當我有時間時,我的目標是將此功能實作到一個編輯器中...]
a[len - 1 - i]
轉換為從 1 開始的編排方式?它會直接將其索引包覆起來,如下所示:a[(len - 1 - i) + 1]
,還是嘗試分析內容,尋找「- 1
」以移除,或「+/- <constant>
」用於變更,如下所示:a[len - i]
?如果程式碼未完成該怎麼辦:a[len - ]
或者乾脆想相反:a[~i & 0xff] /* len == 256 */
?而如何處理 C++,它允許你覆寫 operator []
?另外請考慮,程式設計人員經常在討論程式碼時共用螢幕,這可能會造成極其嚴重的混淆。 MarceloCantos?