[英]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.