[英]List design in functional languages
我注意到在Haskell和OCaml等函數式語言中,您可以使用列表執行2個操作。 首先你可以做x:xs
,其中x是一個元素,而xs是一個列表,結果是我們得到一個新的列表,其中x在常量時間附加到xs的開頭。 第二個是x++y
,其中x和y都是列表,結果是我們得到一個新的列表,其中y在線性時間內相對於x中元素的數量附加到x的末尾。 現在我不是語言設計和編譯器的專家,但在我看來,這很像一個鏈接列表的簡單實現,其中一個指針指向第一個項目。 如果我用C ++這樣的語言實現這個數據結構,我會發現添加一個指向最后一個元素的指針通常是微不足道的。 在這種情況下,如果以這種方式實現這些語言(假設它們確實使用了所描述的鏈接列表),則向最后一項添加“指針”將使得將項添加到列表末尾並且允許模式匹配更有效。最后一個元素。
我的問題是這些數據結構是否真的實現為鏈表,如果是這樣,為什么他們不添加對最后一個元素的引用?
是的,它們確實是鏈表。 但它們是不可改變的。 不變性的優點是您不必擔心還有誰有指向同一列表的指針。 您可以選擇編寫x++y
,但程序中的其他位置可能依賴於x
保持不變。
在這些語言的編譯器上工作的人(我是其中之一)並不擔心這個成本,因為有很多其他數據結構可以提供高效的訪問:
表示為兩個列表的功能隊列提供對兩端的恆定時間訪問以及用於put
和get
操作的分攤的常量時間。
像手指樹這樣更復雜的數據結構可以以非常低的成本提供多種列表訪問。
如果你只是想要恆定時間附加,John Hughes開發了一個優秀,簡單的列表表示為函數,它提供了這一點。 (在Haskell庫中,它們被稱為DList
。)
如果您對這些問題感興趣,可以從Chris Okasaki的書“ Purely Functional Data Structures”和Ralf Hinze的一些不那么令人生畏的論文中獲得很好的信息。
你說:
第二個是
x++y
,其中x和y都是列表,結果是y在線性時間內相對於x中元素的數量附加到x的末尾。
在像Haskell這樣的函數式語言中,這並不是真的。 y被附加到x的副本 ,因為保留在x上的任何東西都取決於它沒有改變。
如果你要復制所有的x,那么抓住它的最后一個節點並沒有真正獲得任何東西。
是的,它們是鏈表。 在像Haskell和OCaml這樣的語言中,您不會在列表末尾添加項目。 列表是不可變的。 有一個操作可以創建新列表 - cons,您之前引用的:
運算符。 它需要一個元素和一個列表,並創建一個新的列表,其中元素為head,列表為tail。 x++y
占用線性時間的原因是因為它必須將x
的最后一個元素與y
,然后使用該列表來構成x
的倒數第二個元素,依此類推x
每個元素。 x
中的cons單元都不能被重用,因為這會導致原始列表也發生變化。 指向x
的最后一個元素的指針在這里不是很有用 - 我們仍然需要遍歷整個列表。
++只是眾多“你可以用列表做的事情”中的一個。 現實情況是,列表非常通用,很少使用其他集合。 此外,我們的函數式程序員幾乎從不覺得需要查看列表的最后一個元素 - 如果需要,最后有一個函數。
但是,僅僅因為列表很方便,這並不意味着我們沒有其他數據結構。 如果您真的感興趣,請查看本書http://www.cs.cmu.edu/~rwh/theses/okasaki.pdf (純功能數據結構)。 您將找到樹,隊列,列表,其中O(1)在尾部附加元素,依此類推。
避免變異狀態的最簡單方法是使用不可變數據結構。 Clojure提供了一組不可變列表,向量,集合和映射。 由於它們無法更改,因此從不可變集合中“添加”或“刪除”某些內容意味着創建一個新集合,就像舊集合一樣,但需要進行必要的更改。 持久性是用於描述屬性的術語,其中舊版本的集合在“更改”之后仍然可用,並且該集合保持其對大多數操作的性能保證。 具體來說,這意味着無法使用完整副本創建新版本,因為這需要線性時間。 不可避免地, 持久集合是使用鏈接數據結構實現的 ,因此新版本可以與先前版本共享結構。 單鏈表和樹是基本的功能數據結構 ,Clojure根據數組映射的哈希嘗試添加哈希映射,集合和向量。
(強調我的)
所以基本上它看起來你大多是正確的,至少就Clojure而言。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.