簡體   English   中英

遞歸修改 Haskell 中的部分數據結構

[英]Recursively modifying parts of a data structure in Haskell

大家好,我是 Haskell 的新手,我想創建一個 Haskell 程序,該程序可以將德摩根定律應用於邏輯表達式。 問題是我不能將給定的表達式更改為新的表達式(在應用德摩根定律之后)

具體來說,這里是我的數據結構

data LogicalExpression = Var Char
        | Neg LogicalExpression
        | Conj LogicalExpression LogicalExpression
        | Disj LogicalExpression LogicalExpression
        | Impli LogicalExpression LogicalExpression
        deriving(Show)

我想創建一個 function 接受“LogicalExpression”並在應用德摩根定律后返回“LogicalExpression”。

例如,每當我在logicalExpression中找到這種模式:Neg ( Conj (Var 'a') (Var 'b') ) 時,我需要將其轉換為 Conj ( Neg (Var 'a') Neg (Var 'b') )。

這個想法很簡單,但在 haskell 中實現起來非常困難,這就像試圖制作一個 function(我們稱之為 Z),它搜索 x 並將其轉換為 y,所以如果給定 Z 為“vx”,它會將其轉換為“vy " 僅在數據結構“logicalExpression”中采用字符串而不是 x 而不是 x 它采用我提到的模式並再次吐出整個logicalExpression,但模式已更改。

PS:我希望 function 采用任何復雜的邏輯表達式並使用德摩根定律對其進行簡化

有什么提示嗎?

提前致謝。

盧克 (luqui) 提出了可能是思考問題的最優雅的方式。 但是,他的編碼要求您為要創建的每個此類重寫規則手動獲取正確的大片遍歷。

Bjorn Bringert 來自A Pattern for Nearly Composable Functions 的組合可以使這更容易,特別是如果您需要編寫多個這樣的規范化通道。 它通常用 Applicatives 或 rank 2 類型編寫,但為了簡單起見,我將推遲:

給定您的數據類型

data LogicalExpression
    = Var Char
    | Neg LogicalExpression
    | Conj LogicalExpression LogicalExpression
    | Disj LogicalExpression LogicalExpression
    | Impl LogicalExpression LogicalExpression
deriving (Show)

我們可以定義一個 class 用於尋找非頂級子表達式:

class Compos a where
    compos' :: (a -> a) -> a -> a

instance Compos LogicalExpression where
    compos' f (Neg e)    = Neg (f e)
    compos' f (Conj a b) = Conj (f a) (f b)
    compos' f (Disj a b) = Disj (f a) (f b)
    compos' f (Impl a b) = Impl (f a) (f b)
    compos' _ t = t

例如,我們可以消除所有影響:

elimImpl :: LogicalExpression -> LogicalExpression
elimImpl (Impl a b) = Disj (Not (elimImpl a)) (elimImpl b)
elimImpl t = compos' elimImpl t -- search deeper

然后我們可以應用它,就像上面的 luqui 一樣,尋找否定的連詞和析取詞。 而且,正如 Luke 指出的那樣,一次完成所有否定分布可能會更好,因此我們還將包括否定蘊涵和雙重否定消除的歸一化,產生否定范式的公式(假設我們已經消除暗示)

nnf :: LogicalExpression -> LogicalExpression
nnf (Neg (Conj a b)) = Disj (nnf (Neg a)) (nnf (Neg b))
nnf (Neg (Disj a b)) = Conj (nnf (Neg a)) (nnf (Neg b))
nnf (Neg (Neg a))    = nnf a
nnf t                = compos' nnf t -- search and replace

關鍵是最后一行,它表示如果上述其他情況均不匹配,則 go 會尋找可以應用此規則的子表達式。 此外,由於我們將Neg推入術語,然后對它們進行規范化,因此您應該只在葉子處使用否定變量,因為Neg在另一個構造函數之前的所有其他情況都將被規范化。

更高級的版本將使用

import Control.Applicative
import Control.Monad.Identity

class Compos a where
    compos :: Applicative f => (a -> f a) -> a -> f a

compos' :: Compos a => (a -> a) -> a -> a
compos' f = runIdentity . compos (Identity . f) 

instance Compos LogicalExpression where
    compos f (Neg e)    = Neg <$> f e
    compos f (Conj a b) = Conj <$> f a <*> f b
    compos f (Disj a b) = Disj <$> f a <*> f b
    compos f (Impl a b) = Impl <$> f a <*> f b
    compos _ t = pure t

這對您的特定情況沒有幫助,但如果您需要返回多個重寫結果,執行IO或在重寫規則中以其他方式參與更復雜的活動,這將很有用。

您可能需要使用它,例如,如果您想嘗試在它們適用的位置的任何子集中應用德摩根定律,而不是追求正常形式。

請注意,無論您正在重寫什么 function,您正在使用 Applicative,甚至是遍歷期間信息流的方向性, compos定義都只需要為每種數據類型提供一次。

如果我理解正確,您想應用德摩根定律將否定盡可能推入樹中。 您必須多次明確地遞歸樹:

-- no need to call self on the top-level structure,
-- since deMorgan is never applicable to its own result
deMorgan (Neg (a `Conj` b))  =  (deMorgan $ Neg a) `Disj` (deMorgan $ Neg b)
deMorgan (Neg (a `Disj` b))  =  (deMorgan $ Neg a) `Conj` (deMorgan $ Neg b)
deMorgan (Neg a)             =  Neg $ deMorgan a
deMorgan (a `Conj` b)        =  (deMorgan a) `Conj` (deMorgan b)
-- ... etc.

術語重寫系統中,所有這一切都會容易得多,但這不是 Haskell 是什么。

(順便說一句,如果您在公式解析器中將P -> Q轉換not P or Q並刪除Impli構造函數,那么生活會變得容易得多。公式中每個 function 中的案例數會變小。)

其他人給出了很好的指導。 但我會將其表述為否定消除器,這意味着你有:

deMorgan (Neg (Var x)) = Neg (Var x)
deMorgan (Neg (Neg a)) = deMorgan a
deMorgan (Neg (Conj a b)) = Disj (deMorgan (Neg a)) (deMorgan (Neg b))
-- ... etc. match Neg against every constructor
deMorgan (Conj a b) = Conj (deMorgan a) (deMorgan b)
-- ... etc. just apply deMorgan to subterms not starting with Neg

我們可以通過歸納看到,在結果中, Neg只會應用於Var項,並且最多一次。

我喜歡將這樣的轉換視為消除器:即試圖通過將某個構造函數向下推來“擺脫”頂層的某個構造函數。 將您正在消除的構造函數與每個內部構造函數(包括其自身)匹配,然后僅轉發 rest。 例如,lambda 微積分評估器是Apply消除器。 SKI 轉換器是Lambda消除器。

重要的一點是 deMorgan 的遞歸應用。 它與(例如)完全不同:

 deMorgan' z@(Var x) = z
 deMorgan' (Neg (Conj x y)) = (Disj (Neg x) (Neg y))
 deMorgan' (Neg (Disj x y)) = (Conj (Neg x) (Neg y))
 deMorgan' z@(Neg x) = z
 deMorgan' (Conj x y) = Conj x y
 deMorgan' (Disj x y) = Disj x y

這不起作用:

 let var <- (Conj (Disj (Var 'A') (Var 'B')) (Neg (Disj (Var 'D') (Var 'E'))))
 *Main> deMorgan' var
 Conj (Disj (Var 'A') (Var 'B')) (Neg (Disj (Var 'D') (Var 'E')))

這里的問題是您沒有在子表達式(x 和 ys)中應用轉換。

暫無
暫無

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

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