簡體   English   中英

如何避免 zipWith 自引用中的無限循環?

[英]How to avoid infinite loop in zipWith a self reference?

我想創建一個列表數據結構,它可以 zipWith 具有更好的自引用行為。 這是一種深奧的語言,它將依靠自我引用和惰性來僅使用值(無用戶函數)來實現圖靈完備。 我已經創建了它,稱為Atlas但它有很多內置插件,我想減少它並能夠在 Haskell 中編譯/解釋。

問題是 zipWith 檢查任一列表是否為空並返回空。 但是如果這個答案取決於 zipWith 的結果,那么它將無限循環。 本質上,我希望它能夠檢測到這種情況,並相信該列表不會為空。 這是一個使用 DList 的例子

import Data.DList
import Data.List (uncons)

zipDL :: (a->b->c) -> DList a -> DList b -> DList c
zipDL f a b = fromList $ zipL f (toList a) (toList b)

zipL :: (a->b->c) -> [a] -> [b] -> [c]
zipL _ [] _ = []
zipL _ _ [] = []
zipL f ~(a:as) ~(b:bs) = f a b : zipL f as bs

a = fromList [5,6,7]

main=print $ dh where
   d = zipDL (+) a $ snoc (fromList dt) 0
   ~(Just (dh,dt)) = uncons $ toList d

除問題外,此代碼將對列表 5、6、7 求和。 它可以通過刪除zipL _ _ [] = []來修復,因為它假定結果不會為空,然后它實際上證明不是空的。 但這是一個糟糕的解決方案,因為我們不能總是假設它是第二個可以具有自引用的列表。

另一種解釋方式是我們討論這些列表的大小。

zip 的大小 ab = min (size a) (size b)

所以在這個例子中:size d = min (size a) (size d-1+1)

但問題在於,如果 d 的大小為 0,則 d 的大小 = 0,但如果 d 的大小為 1,則大小為 1,但是一旦 d 的大小被認為大於 a 的大小,那么大小就是a,這是矛盾的。 但是任何大小 0-a 都有效,這意味着它是未定義的。

本質上我想檢測這種情況並使 d = a 的大小。

到目前為止,我唯一想到的就是將所有列表設為 Maybe,並以 Nothing 值終止列表。 然后在 zipWith 二進制文件 function 的應用程序中,如果任一值為 Nothing,則返回 Nothing。 然后你可以去掉 zip 中的兩個 [] 檢查,因為你可以認為所有列表都是無限的。 最后,為了使求和示例起作用,不執行 snoc,而是執行 map,並將任何 Nothing 值替換為 snoc 值。 這是有效的,因為當檢查第二個列表是否為 Nothing 時,它可以延遲返回 true,因為第二個列表的任何值都不能為空。

這是代碼:

import Data.Maybe

data L a = L (Maybe a) (L a)

nil :: L a
nil = L Nothing nil

fromL :: [a] -> L a
fromL [] = nil
fromL (x:xs) = L (Just x) (fromL xs)

binOpMaybe :: (a->b->c) -> Maybe a -> Maybe b -> Maybe c
binOpMaybe f Nothing _ = Nothing
binOpMaybe f _ Nothing = Nothing
binOpMaybe f (Just a) (Just b) = Just (f a b)

zip2W :: (a->b->c) -> L a -> L b -> L c
zip2W f ~(L a as) ~(L b bs) = L (binOpMaybe f a b) (zip2W f as bs)

unconsL :: L a -> (Maybe a, Maybe (L a))
unconsL ~(L a as) = (a, Just as)

mapOr :: a -> L a -> L a
mapOr v ~(L a as) = L (Just $ fromMaybe v a) $ mapOr v as

main=print $ h
   where
   a = fromL [4,5,6]
   b = zip2W (+) a (mapOr 0 (fromJust t))
   (h,t) = unconsL $ b

這種方法的缺點是它需要其他操作員使用Just. fromMaybe initialvalue Just. fromMaybe initialvalue 這是一個不如++直觀的運算符。 如果沒有它,該語言可以完全建立在++ uncons(:[])之上,這將非常簡潔。

我發現的另一件事是在當前的 ruby 實現中,當值依賴於自身時拋出錯誤,並在空列表檢測中捕獲它。 但這有點老套而且不完全合理,盡管它確實適用於這樣的情況。 我認為這在 Haskell 中不起作用,因為我認為您無法檢測到自我依賴?

對於冗長的描述和非常奇怪的用例,我們深表歉意。 我花了很多時間思考這個問題,但還沒有解決,也無法再簡潔地解釋它,不期待答案,但認為值得一試。 感謝您的考慮。

編輯:在將其視為最大的不動點問題之后,這似乎是一個糟糕的問題,因為沒有針對此類問題的有效通用解決方案。 例如,假設代碼是b = zipWith (+) a (if length b < 1 then [1] else [])

出於我的目的,正確處理某些情況仍然很好 - 提供的示例確實有解決方案。 所以我可以將問題重新定義為:我們什么時候可以有效地找到最大的不動點,那個不動點是什么? 但我相信這樣的問題沒有簡單的答案,因此編程語言依賴臨時規則的基礎很差。

聽起來你想要一個最大的固定點。 我不確定我以前是否見過這樣做,但也許可以為支持這些類型的類型創建一個合理的類型 class。

class GF a where gfix :: (a -> a) -> a

instance GF a => GF [a] where
    gfix f = case (f (repeat undefined), f []) of
        (_:_, _) -> b:bs where
            b = gfix (\a' -> head (f (a':bs)))
            bs = gfix (\as' -> tail (f (b:as')))
        ([], []) -> []
        _ -> error "no fixed point greater than bottom exists"

-- use the usual least fixed point. this ain't quite right, but
-- it works for this example, and maybe it's Good Enough
instance GF Int where gfix f = let x = f x in x

在 ghci 中嘗試一下:

> gfix (\xs -> zipWith (+) [5,6,7] (tail xs ++ [0])) :: [Int]
[18,13,7]

這種實現並不是特別有效。 例如,將[5,6,7]替換為[1..n]會導致運行時在n中為二次方。 也許有一些可以改進的聰明才智,但對我來說,go 的效果並不是很明顯。

我對這個具體案例有一個答案,而不是一般的。

appendRepeat :: a -> [a] -> [a]
appendRepeat v a = h : appendRepeat v t
   where
      ~(h,t) =
         if null a
         then (v,[])
         else (head a,tail a)

a = [4,5,6]

main=print $ head b
   where
   b = zipWith (+) a $ appendRepeat 0 (tail b)

appendRepeat將重復值的無限列表添加到列表的末尾。 但關鍵是它在決定返回一個非空列表時不檢查列表是否為空,其中尾部是遞歸調用。 這樣,懶惰就永遠不會在檢查zipWith _ []案例的無限循環中結束。

所以這段代碼有效,並且出於原始問題的目的,它可用於將語言轉換為僅使用 2 個簡單函數( ++:[] )。 但是解釋器需要做一些 static 分析來追加一個重復值,並將其替換為使用這個特殊的appendRepeat function(這在 Atlas 中很容易完成)。 只使這個實現切換似乎很老套,但這就是所需要的。

暫無
暫無

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

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