簡體   English   中英

在 Haskell 中,為什么沒有一個 TypeClass 來處理可以像列表一樣的東西?

[英]In Haskell, why isn't there a TypeClass for things that can act like lists?

我正在閱讀Learn You a Haskell ,我想知道為什么這么多東西都像一個列表,而 Prelude 中沒有任何內容使用類型類的本機工具來設置它:

“ : 的字節串版本被稱為 cons 它接受一個字節和一個字節串並將該字節放在開頭。雖然它很懶惰,因此即使字節串中的第一個塊未滿,它也會創建一個新塊。這就是為什么如果要在字節串的開頭插入大量字節,最好使用 cons, cons' 的嚴格版本。”

為什么沒有可列出的TypeClass或提供:函數來統一Data.ByteStringData.ListData.ByteString.Lazy等的東西? 這是否有原因,或者這只是遺留 Haskell 的一個元素? 使用:作為例子有點輕描淡寫,同樣來自 LYAH:

否則,字節串模塊有大量類似於 Data.List 中的函數,包括但不限於 head、tail、init、null、length、map、reverse、foldl、foldr、concat、takeWhile、filter , 等等。

ListLike包似乎提供了您正在尋找的東西。 我一直不明白為什么它不更受歡迎。

除了 List 之外,在 Prelude 中沒有實現這一點的一個原因是,如果不調用某些語言擴展(多參數類型類和基金或關聯類型),就不可能做得很好。 需要考慮三種容器:

  1. 根本不關心其元素的容器(例如 [])
  2. 僅針對特定元素(例如字節串)實現的容器
  3. 對元素多態但需要上下文的容器(例如 Data.Vector.Storable,它將保存具有可存儲實例的任何類型)。

這是一個非常基本的 ListLike 風格的類,沒有使用任何擴展:

class Listable container where
  head :: container a -> a

instance Listable [] where
  head (x:xs) = x

instance Listable ByteString where --compiler error, wrong kind

instance Listable SV.Vector where
  head v = SV.head    --compiler error, can't deduce context (Storable a)

這里container有種類*->* 這不適用於字節串,因為它們不允許任意類型; 他們有種* 它也不適用於 Data.Vector.Storable 向量,因為該類不包含上下文(Storable 約束)。

您可以通過將類定義更改為

class ListableMPTC container elem | container -> elem where

或者

class ListableAT container where
  type Elem container :: *

現在container有 kind * ; 它是一個完全應用的類型構造函數。 也就是說,您的實例看起來像

instance ListableMPTC [a] a where

但你不再是 Haskell98。

這就是為什么即使是一個簡單的 Listable 類型的接口也是不平凡的。 當您要考慮不同的集合語義(例如隊列)時,它會變得有點困難。 另一個真正的大挑戰是可變數據與不可變數據。 到目前為止,我所看到的每一次嘗試(除了一次)都通過創建一個可變接口和一個不可變接口來解決這個問題。 我所知道的將兩者統一起來的一個界面令人費解,調用了一堆擴展,並且性能很差。

附錄:字節串

完全是我的猜想,但我認為我們堅持將字節串作為進化的產物。 也就是說,它們是低性能 I/O 操作的第一個解決方案,使用Ptr Word8 s 與 IO 系統調用接口是有意義的。 對指針的操作需要 Storable,而且很可能當時還沒有使多態起作用的必要擴展(如上所述)。 現在很難克服他們的勢頭。 具有多態性的類似容器當然是可能的,storablevector 包實現了這一點,但它並沒有那么流行。

字節串可以是多態的,對元素沒有任何限制嗎? 我認為最接近 Haskell 的是 Array 類型。 這幾乎不如低級 IO 的字節串好,因為數據需要從指針解包到數組的內部格式。 此外,數據是裝箱的,這增加了顯着的空間開銷。 如果你想要未裝箱的存儲(更少的空間)和與 C 的高效接口,指針是要走的路。 一旦你有了 Ptr,你就需要 Storable,然后你需要在類型類中包含元素類型,這樣你就需要擴展了。

話雖如此,我認為有了適當的擴展可用,對於任何單個容器實現(模可變/不可變 API)來說,這基本上是一個已解決的問題。 現在更難的部分是提出一組合理的類,這些類可用於許多不同類型的結構(列表、數組、隊列等),並且足夠靈活以供使用。 我個人認為這相對簡單,但我可能是錯的。

這樣一個類的主要問題是,即使它存在,它也只能提供表面的相似性。

使用不同結構構建的相同算法的漸近性會有很大差異。

在嚴格字節串的情況下,用 cons 構建它們是可怕的,因為每次添加另一個 Char 時,你最終都會復制整個字符串。 列表上的此O(1)操作將其轉換為 Bytestring 上的O(n)操作。

當您實現可能想到的第一個算法 map 時,這會導致O(n^2)行為,而使用 cons 構建列表或 Data.Sequence.Seq 是線性時間,它可以在O(n) 中實現對於字節串或向量以及稍加思考。

事實證明,鑒於這一點,這樣一個類的效用比實際更膚淺。

我並不是說找不到好的設計,但這樣的設計很難使用和優化,而且設計的可用版本很可能不會成為 Haskell 98。

我已經在我的 keys 包中擠出了這個設計空間的一部分,它提供了很多用於索引容器等的功能,但我故意避免提供類似列表的 API a.) 因為它之前已經完成了由於上述漸近問題,幾乎沒有成功和 b.)。

tl;dr當基礎操作的漸近線發生變化時,您通常希望以非常不同的方式實現算法。

提供:功能來統一 Data.ByteString、Data.List、Data.ByteString.Lazy 等?

已經嘗試提出一個好的 a) 序列接口和 b) 容器接口,但是,統一不同類型的數據類型,具有不同的類型約束,通常導致結果不夠標准,難以想象將它們放在基礎庫中。 與數組類似,盡管 Vector 包現在有一個相當通用的接口(基於關聯的數據類型)。

有幾個項目將這些不同的半相關數據類型統一到一個單一的接口中,所以我希望我們很快就能看到結果。 容器類型也是如此。 結果不會是微不足道的。

有兩種類型的類,稱為FoldableTraversable ,旨在抽象列表和其他順序數據結構的一些常見行為。 並非所有數據結構都有這些實例,我不知道它們對編譯器是否足夠透明,以便它仍然可以對它們執行優化(有人知道嗎?)

資料來源:可折疊和可遍歷
另請參閱為什么 Haskell 缺少“明顯的”類型類的答案

實際上在GHC.Exts有一個OverloadedLists擴展和一個IsList類來配合它。

ByteString 不是泛型類型。

在其他語言中,所有類似列表的數據結構都有類似Sequence東西。 我認為這有效,具有正確的擴展名:

class Seq a b | a -> b where
  head :: a -> b
  isTail :: a -> Bool

# ([a]) is a sequence of a's
instance Seq [a] a where
  head (x:xs) = x
  isTail = (== [])

# ByteString is a sequence of chars
instance Seq ByteString Char

或者試試這個?

type BS a = ByteString
instance List BS

在 Haskell 中為類似列表的數據創建一個類型類並沒有多大價值。 為什么? 因為懶惰。 您只需編寫一個將數據轉換為列表的函數,然后使用該列表即可。 該列表只會在需要其子列表和元素時構建,並且一旦沒有對前綴的引用,它們的內存將有資格進行收集。

提供泛型toList函數的類型類是toList ——但是,它已經存在於Data.Foldable

所以基本上,解決方案是實現Data.Foldable並使用其toList函數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM