[英]Is equality testing possible between two infinite data structure in Haskell?
在我正在研究的項目中,某種類型的數據有時可能包含在其中。 例如,
data Example = Apple Int
| Pear Int Example
a = Pear 10 a
b = Pear 10 b
作為程序員,我知道a
和b
是相等的,但是當我實際測試它們之間的相等時,它會無限循環,因為它們的值需要進行評估以進行比較。
有沒有其他方法可以在這些數據之間進行相等測試? 或者有沒有辦法避免像這樣的問題?
如果這是你想做的事情,我想談談如何真正做到這一點。 這比你最初想象的要難 - 而且比第二次猜的更容易! 為了好玩,我會稍微努力解決問題; 我們最終將代表價值觀
a = Pear 10 a
b = Pear 10 (Pear 10 b)
c = Apple 10
並且計算a
和b
是“相等的” - 為了精確的平等感,我們將在下面討論。 這不是可觀察共享必然會給你免費的東西。
我們將在續集中使用的確切的平等感是兩個相似性。 通常,雙相似性 - 及其近親,互模擬 - 表示為兩個標記圖之間的關系。 出於我們的目的,您應該在圖中描繪包含我們數據類型的當前構造函數中的一些數據的圖中的節點,以及指向子項的邊。 因此,對於Pear 10 (Pear 20 (Apple 30))
,我們可能有Pear 10 -> Pear 20 -> Apple 30
; 並且上面的值b
將是循環圖
Pear 10 -> Pear 10
^ |
\_______/
可觀察共享你會得到這么遠,但是不會讓你明白如何確定這兩個圖是等價的:
Pear 10 -. Pear 10 -> Pear 10
^ | ~= ^ |
\____/ \_______/
如果您熟悉用於最小化DFA的算法,您可以在此停止; 這些算法很容易適應測試常規語言的平等,這基本上就是我們下面要做的。
關鍵的見解是,上面兩個圖中的所有三個節點的行為基本相同 - 從左圖中的節點開始可以采用的任何路徑在右圖中都具有“相同”路徑。 也就是說,假設我們在滿足此屬性的左右圖中的節點之間存在關系R:
if nl R nr
and there is an edge (nl, e, nl') in the graph,
then there is an edge (nr, e, nr') in the graph
and nl' R nr'
我們稱R為互模擬。 最大的關系R將被稱為雙相似性。 如果兩個圖中的“根”節點是相似的,那么相應的Haskell值是相等的! 在這一點上,我希望你已經達到了問題似乎比你最初猜到的更難的程度; 也許不可能。 畢竟,我們如何才能掌握最大的這種關系呢?
一個答案是從完整關系開始並刪除違反上述約束的任何節點對。 繼續迭代該過程,直到沒有任何變化,並看看我們還剩下什么。 事實證明,你可以證明這個過程實際上產生了相似性! 我們將以一種非常天真的方式實現這一點; 如果你想要更快的速度,你可以谷歌關於有效的雙相似性算法。
首先是序言。 我們將使用fgl包來表示圖形。
import Control.Monad.Reader
import Data.Graph.Inductive hiding (ap)
import Data.Map (Map)
import Data.Set (Set)
import qualified Data.Map as M
import qualified Data.Set as S
fgl包為Node
的標識定義了一個類型Node
。 我們將我們的關系簡單地表示為
type Relation = Set (Node, Node)
首先,我們想要一對圖的完整關系。 雖然我們正在努力,但我們不妨切斷任何節點標簽不匹配的對。 (關於我選擇的命名約定的注釋:在fgl中,每個節點和邊都有一個標簽 - 可以是你喜歡的任何類型 - 和一個標識 - 必須是Node
或Edge
類型。我們的名字將反映這一點在可能的情況下:節點的n
前綴,邊的e
,身份的i
和標簽/值的v
。我們將使用l
和r
作為左手和右手圖的后綴。)
labeledPairs :: (Eq n, Graph gr) => gr n e -> gr n e' -> Relation
labeledPairs l r = S.fromList
[ (nil, nir)
| (nil, nvl) <- labNodes l
, (nir, nvr) <- labNodes r
, nvl == nvr
]
現在,下一部分是檢查兩個節點是否滿足我們上面描述的“單步相關性”條件。 也就是說,對於其中一個節點的每個邊緣,我們正在尋找具有相同標簽的另一個邊緣,並導致我們當前聲稱與之相關的另一個節點。 將此搜索轉換為Haskell:
-- assumption: nil and nir are actual nodes in graphs l and r, respectively
ssRelated :: (Ord e, Graph gr) => gr n e -> gr n e -> Relation -> Node -> Node -> Bool
ssRelated l r rel nil nir = rell && relr where
el = out l nil
er = out r nil
mel = M.fromListWith (++) [(evl, [nil]) | (_, nil, evl) <- el]
mer = M.fromListWith (++) [(evr, [nir]) | (_, nir, evr) <- er]
rell = and
[ or [(nil, nir) `S.member` rel | nir <- M.findWithDefault [] evl mer]
| (_, nil, evl) <- el
]
relr = and
[ or [(nil, nir) `S.member` rel | nil <- M.findWithDefault [] evr mel]
| (_, nir, evr) <- er
]
我們現在可以編寫一個函數來檢查每對節點的單步適用性:
prune :: (Ord e, Graph gr) => gr n e -> gr n e -> Relation -> Relation
prune l r rel = S.filter (uncurry (ssRelated l r rel)) rel
為了計算相似性,如上所述,我們將從完整關系開始,並反復修剪不符合標准的節點。
bisimilarity :: (Eq n, Ord e, Graph gr) => gr n e -> gr n e -> Relation
bisimilarity l r
= fst . head
. dropWhile (uncurry (/=))
. ap zip tail
. iterate (prune l r)
$ labeledPairs l r
現在我們可以通過在每個圖中挑選根節點並檢查它們是否具有相似性來檢查兩個圖是否具有相同的無限展開:
-- assumption: the root of the graph is node 0
bisimilar :: (Eq n, Ord e, Graph gr) => gr n e -> gr n e -> Bool
bisimilar l r = (0, 0) `S.member` bisimilarity l r
現在讓我們看看它在行動! 我們將在圖表表示的答案中更早地制作a
, b
和c
類比。 由於我們的數據類型只有一個可能的遞歸出現,我們不需要有趣的邊緣標簽。 mkGraph
函數獲取標記節點列表和標記邊緣列表,並mkGraph
構建圖形。
data NodeLabel = Apple Int | Pear Int
deriving (Eq, Ord, Read, Show)
type EdgeLabel = ()
a, b, c :: Gr NodeLabel EdgeLabel
a = mkGraph [(0, Pear 10)] [(0, 0, ())]
b = mkGraph [(0, Pear 10), (1, Pear 10)] [(0, 1, ()), (1, 0, ())]
c = mkGraph [(0, Apple 10)] []
在ghci:
*Main> bisimilar a b
True
*Main> bisimilar a c
False
*Main> bisimilar b c
False
*Main> bisimilar a a
True
*Main> bisimilar b b
True
*Main> bisimilar c c
True
看起來不錯! 快速將它連接到一個可觀察共享的庫是留給讀者的練習。 請記住,雖然這種方法可以處理具有無限展開的圖形,但是當然可能無法以這種方式處理無限圖形!
一般來說:沒有。 這種平等測試減少了停頓問題。 一種看待這種情況的方法是,我們可以將某些輸入上的圖靈機的執行表達為像這樣的無限數據結構。 查看它的另一種方式是惰性數據結構可以表示任意暫停的計算。
避免這種問題的唯一真正方法是對數據結構設置一些額外的限制,或者檢查比平等更有限的東西。
第一種方法的一個示例是使用某種類型的引用顯式顯示數據類型中的循環,讓您在進行時檢測它們。 這將完全限制您可以用數據結構表達的內容,但也會讓您的等式謂詞檢測循環。 我認為你可以用可觀察的分享來做到這一點; 看一下這樣做的包的data-reify 。 這種方法應該使得檢查一個非常直接的遞歸結構,就像您的示例一樣簡單。
對於第二種方法,您可以使用一個返回Maybe Bool
的函數:如果它無法確定兩個結構在X步驟中是否相等,則返回Nothing
。 根據您的數據類型的創建方式,您可以確保具有相同前綴的超出特定大小的任何類型幾乎總是相等並且僅依賴於此。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.