簡體   English   中英

在編程的上下文中什么構成了 codata?

[英]What constitutes codata in the context of programming?

這是一個核心遞歸算法,因為每次迭代它都會調用比之前更大的數據:

iterate f x =  x : iterate f (f x)

它類似於尾遞歸累加器樣式,但它的累加器是隱式的,而不是作為參數傳遞。 如果不是因為懶惰,它將是無限的。 那么 codata 只是 WHNF 中的值構造函數的結果,有點像(a, thunk)嗎? 或者 codata 是范疇論中的一個數學術語,在編程領域沒有有用的表示?

后續問題:值遞歸只是核心遞歸的同義詞嗎?

我認為回答你的問題需要很多解釋,所以這里有一個很長的答案,最后是你問題的具體答案。

數據和余數據在范疇論方面具有正式的數學定義,因此這不僅僅是它們在程序中的使用方式(即,不僅僅是您在評論中提到的“應用程序上下文”)。 在 Haskell 中看起來可能是這種方式,因為語言的特性(特別是非終止和惰性)最終會模糊區分,所以在 Haskell 中,所有數據也是 codata ,反之亦然,但不一定是這樣,並且有些語言可以使區分更加清晰。

data 和codata在編程領域都有有用的表示,並且這些表示產生了與遞歸和核心遞歸的自然關系。

如果不快速掌握技術知識,很難解釋這些正式的定義和表示,但粗略地說,整數列表的數據類型是類型L和構造函數 function:

makeL :: Either () (Int, L) -> L

這在某種程度上是“普遍的”,因為它可以完全代表任何這樣的結構。 (在這里,您想將 LHS 類型Either () (Int, L)解釋為列表L是空列表Left ()或由頭元素h:: Int組成的對Right (h, t)和一個尾列表t:: L 。)

從一個反例開始, L = Bool不是我們要尋找的數據類型,因為即使你可以這樣寫:

foo :: Either () (Int, Bool) -> Bool
foo (Left ()) = False
foo (Right (h, t)) = True

“構造”一個Bool ,這不能完全代表任何這樣的構造。 例如,兩種結構:

foo (Right (1, foo (Left ()))) = True
foo (Right (2, foo (Left ()))) = True

給出相同的Bool值,即使它們使用不同的整數,所以這個Bool值不足以完全表示構造。

相比之下,類型[Int]合適的數據類型,因為(幾乎是微不足道的)構造函數 function:

makeL :: Either () (Int, [Int]) -> [Int]
makeL (Left ()) = []
makeL (Right (h, t)) = h : t

完全代表任何可能的構造,為每個構造創造獨特的價值。 因此,它在某種程度上是類型簽名Either () (Int, L) -> L的“自然”構造。

類似地,整數列表的余數據類型將是L類型以及析構函數 function:

eatL :: L -> Either () (Int, L)

這在某種程度上是“普遍的”,因為它可以代表任何可能的破壞。

同樣,從一個反例開始,一對(Int, Int)不是我們正在尋找的余數據類型。 例如,使用析構函數:

eatL :: (Int, Int) -> Either () (Int, (Int, Int))
eatL (a, b) = Right (a, (b, a))

我們可以表示破壞:

let p0 = (1, 2)
    Right (1, p1) = eatL p0
    Right (2, p2) = eatL p1
    Right (1, p3) = eatL p2
    Right (2, p4) = eatL p3
...continue indefinitely or stop whenever you want...

但我們不能代表破壞:

let p0 = (?, ?)
    Right (1, p1) = eatL p0
    Right (2, p2) = eatL p1
    Right (3, p3) = eatL p2
    Left () = eatL p3

另一方面,在 Haskell 中,列表類型[Int]是整數列表的合適余數據類型,因為析構函數:

eatL :: [Int] -> Either () (Int, [Int])
eatL (x:xs) = Right (x, xs)
eatL [] = Left ()

可以表示任何可能的破壞(包括有限或無限破壞,這要歸功於 Haskell 的惰性列表)。

(作為證據表明這並不全是揮手,如果你想把它與形式數學聯系起來,在技術范疇理論術語中,上面等同於說類似列表的內函子:

F(A) = 1 + Int*A   -- RHS equivalent to "Either () (Int,A)"

產生一個類別,其對象是構造函數(AKA F-algebras) 1 + Int*A -> A 與 F 相關的數據類型是該類別中的初始 F 代數。 F 還產生了另一個類別,其對象是析構函數(AKA F-coalgebras) A -> 1 + Int*A 與 F 相關的余數據類型是該類別中的最終 F-余代數。)

直觀地說,正如@DanielWagner 所建議的那樣,數據類型是表示類似列表的 object 的任何構造的一種方式,而 codata 類型是表示類似列表的 object 的任何破壞的方式。 在 data 和 codata 不同的語言中,存在基本的不對稱性——終止程序只能構造一個有限列表,但它可以破壞(第一部分)一個無限列表,因此 data 必須是有限的,但 codata 可以是有限的或無限。

這導致了另一個並發症。 在 Haskell 中,我們可以使用makeL構造一個無限列表,如下所示:

myInfiniteList = let t = makeL (Right (1, t)) in t

請注意,如果 Haskell 不允許對非終止程序進行惰性求值,則這是不可能的。 因為我們可以做到這一點,根據“數據”的正式定義,一個 Haskell 整數列表數據類型還必須包含無限列表,即。 Haskell “數據”可以是無限的。

這可能與您可能在其他地方讀到的內容相沖突(甚至與@DanielWagner 提供的直覺相沖突),其中“數據”僅用於指代有限的數據結構。 好吧,因為 Haskell 有點奇怪,並且因為在數據和余數據不同的其他語言中不允許無限數據,所以當人們談論“數據”和“余數據”(即使在 Haskell 中)並且有興趣區分時,他們可能只使用“數據”來指代有限結構。

遞歸和核心遞歸適應這一點的方式是普遍性屬性自然地給了我們“遞歸”來消費數據和“核心遞歸”來產生codata。 如果L是具有構造函數 function 的整數列表數據類型:

makeL :: Either () (Int, L) -> L

那么使用列表L產生Result的一種方法是定義一個(非遞歸)function:

makeResult :: Either () (Int, Result) -> Result

這里, makeResult (Left ())給出了一個空列表的預期結果,而makeResult (Right (h, t_result))給出了一個列表的預期結果,該列表的頭元素是h:: Int ,其尾部將給出結果t_result:: Result

通過普遍性(即makeL是初始 F 代數的事實),存在一個唯一的 function process:: L -> Result “實現” makeResult 在實踐中,它將遞歸地實現:

process :: [Int] -> Result
process [] = makeResult (Left ())
process (h:t) = makeResult (Right (h, process t))

相反,如果L是帶有析構函數 function 的整數列表余數據類型:

eatL :: L -> Either () (Int, L)

那么從Seed生成列表L的一種方法是定義一個(非遞歸)function:

unfoldSeed :: Seed -> Either () (Int, Seed)

在這里,展開種子應該為每個所需的unfoldSeed生成一個Right (x, nextSeed) ,並生成Left ()來終止列表。

通過普遍性(即, eatL是最終的 F-coalebra 的事實),存在一個獨特的 function generate:: Seed -> L “實現” unfoldSeed 在實踐中,它將以核心遞歸方式實現:

generate :: Seed -> [Int]
generate s = case unfoldSeed s of
  Left () -> []
  Right (x, s') -> x : generate s'

因此,綜上所述,以下是您最初問題的答案:

  • 從技術上講, iterate f是核心遞歸的,因為它是唯一的產生 codata 的 function Int -> [Int]實現:

     unfoldSeed:: Seed -> Either () (Int, Seed) unfoldSeed x = Right (x, fx)

    通過上述定義的generate

  • 在 Haskell 中,產生類型[a]的 codata 的 corecursion 依賴於惰性。 然而,嚴格的尾數據表示是可能的。 例如,以下 codata 表示在 Strict Haskell 中可以正常工作,並且可以安全地進行全面評估。

     data CoList = End | CoList Int (() -> CoList)

    以下 corecursive function 產生一個CoList值(我把它設為有限只是為了好玩——它也很容易產生無限的 codata 值):

     countDown:: Int -> CoList countDown n | n > 0 = CoList n (\() -> countDown (n-1)) | otherwise = End
  • 所以,不,codata 不僅僅是 WHNF 中具有(a, thunk)或類似形式的值的結果,並且 corecursion 不是值遞歸的同義詞。 但是,WHNF 和 thunk 提供了一種可能的實現,並且是實現級別的原因,即“標准”Haskell 列表數據類型也是余數據類型。

暫無
暫無

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

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