![](/img/trans.png)
[英]Is pattern matching in Set Comprehensions in Haskell possible, or what is an alternative?
[英]Cleaner Alternative to Extensive Pattern Matching in Haskell
現在,我有一些基本上像這樣的代碼:
data Expression
= Literal Bool
| Variable String
| Not Expression
| Or Expression Expression
| And Expression Expression
deriving Eq
simplify :: Expression -> Expression
simplify (Literal b) = Literal b
simplify (Variable s) = Variable s
simplify (Not e) = case simplify e of
(Literal b) -> Literal (not b)
e' -> Not e'
simplify (And a b) = case (simplify a, simplify b) of
(Literal False, _) -> Literal False
(_, Literal False) -> Literal False
(a', b') -> And a' b'
simplify (Or a b) = case (simplify a, simplify b) of
(Literal True, _) -> Literal True
(_, Literal True) -> Literal True
(a', b') -> Or a' b'
還有更多關於可以簡化布爾表達式的所有方式的模式。 然而,隨着我添加更多運營商和規則,這種情況越來越大,感覺很笨拙。 特別是因為一些規則需要加兩次來解釋交換性。
我怎樣才能很好地重構許多模式,其中一些(大多數,我會說)甚至是對稱的(例如,采用And和Or模式)?
現在,添加一個規則來簡化And (Variable "x") (Not (Variable "x"))
到Literal False
需要我添加兩個嵌套規則,這幾乎是最優的。
基本上問題是你必須一遍又一遍地寫出每個子句中子表達式的simplify
。 在考慮涉及頂級運營商的法律之前,首先完成所有子表達式會更好。 一種簡單的方法是添加一個simplify
的輔助版本,它不會遞歸:
simplify :: Expression -> Expression
simplify (Literal b) = Literal b
simplify (Variable s) = Variable s
simplify (Not e) = simplify' . Not $ simplify e
simplify (And a b) = simplify' $ And (simplify a) (simplify b)
simplify (Or a b) = simplify' $ Or (simplify a) (simplify b)
simplify' :: Expression -> Expression
simplify' (Not (Literal b)) = Literal $ not b
simplify' (And (Literal False) _) = Literal False
...
由於您在布爾運算中只進行了少量操作,這可能是最明智的方法。 但是,通過更多操作, simplify
的重復可能仍然值得避免。 為此,您可以將一元和二元操作混合到一個公共構造函數:
data Expression
= Literal Bool
| Variable String
| BoolPrefix BoolPrefix Expression
| BoolInfix BoolInfix Expression Expression
deriving Eq
data BoolPrefix = Negation
data BoolInfix = AndOp | OrOp
然后你就是
simplify (Literal b) = Literal b
simplify (Variable s) = Variable s
simplify (BoolPrefix bpf e) = simplify' . BoolPrefix bpf $ simplify e
simplify (BoolInfix bifx a b) = simplify' $ BoolInfix bifx (simplify a) (simplify b)
顯然這會使simplify'
更加尷尬,所以也許不是一個好主意。 但是,您可以通過定義專門的模式同義詞來解決這種語法開銷:
{-# LANGUAGE PatternSynonyms #-}
pattern Not :: Expression -> Expression
pattern Not x = BoolPrefix Negation x
infixr 3 :∧
pattern (:∧) :: Expression -> Expression -> Expression
pattern a:∧b = BoolInfix AndOp a b
infixr 2 :∨
pattern (:∨) :: Expression -> Expression -> Expression
pattern a:∨b = BoolInfix OrOp a b
就此而言,也許也是如此
pattern F, T :: Expression
pattern F = Literal False
pattern T = Literal True
有了它,你就可以寫了
simplify' :: Expression -> Expression
simplify' (Not (Literal b)) = Literal $ not b
simplify' (F :∧ _) = F
simplify' (_ :∧ F) = F
simplify' (T :∨ _) = T
simplify' (a :∧ Not b) | a==b = T
...
我應該添加一個警告: 當我嘗試類似於那些模式同義詞的東西,而不是布爾值但是仿射映射時,它使編譯器非常慢 。 (此外,GHC-7.10尚未支持多態模式同義詞;截至目前,這已經發生了很大變化。)
還要注意,所有這些通常不會產生最簡單的形式 - 為此,您需要找到simplify
的固定點。
你可以做的一件事是在你構造時簡化,而不是先構建然后重復破壞。 所以:
module Simple (Expression, true, false, var, not, or, and) where
import Prelude hiding (not, or, and)
data Expression
= Literal Bool
| Variable String
| Not Expression
| Or Expression Expression
| And Expression Expression
deriving (Eq, Ord, Read, Show)
true = Literal True
false = Literal False
var = Variable
not (Literal True) = false
not (Literal False) = true
not x = Not x
or (Literal True) _ = true
or _ (Literal True) = true
or x y = Or x y
and (Literal False) _ = false
and _ (Literal False) = false
and x y = And x y
我們可以在ghci中嘗試一下:
> and (var "x") (and (var "y") false)
Literal False
請注意,構造函數不會導出:這可以確保構造其中一個的人無法避免簡化過程。 實際上,這可能是一個缺點; 偶爾看到“完整”形式很高興。 處理此問題的標准方法是使導出的智能構造函數成為類類的一部分; 您可以使用它們來構建“完整”表單或“簡化”表單。 為了避免必須兩次定義類型,我們可以使用newtype或phantom參數; 我會選擇后者來減少模式匹配中的噪音。
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
module Simple (Format(..), true, false, var, not, or, and) where
import Prelude hiding (not, or, and)
data Format = Explicit | Simplified
data Expression (a :: Format)
= Literal Bool
| Variable String
| Not (Expression a)
| Or (Expression a) (Expression a)
| And (Expression a) (Expression a)
deriving (Eq, Ord, Read, Show)
class Expr e where
true, false :: e
var :: String -> e
not :: e -> e
or, and :: e -> e -> e
instance Expr (Expression Explicit) where
true = Literal True
false = Literal False
var = Variable
not = Not
or = Or
and = And
instance Expr (Expression Simplified) where
true = Literal True
false = Literal False
var = Variable
not (Literal True) = false
not (Literal False) = true
not x = Not x
or (Literal True) _ = true
or _ (Literal True) = true
or x y = Or x y
and (Literal False) _ = false
and _ (Literal False) = false
and x y = And x y
現在在ghci中,我們可以通過兩種不同的方式“運行”相同的術語:
> :set -XDataKinds
> and (var "x") (and (var "y") false) :: Expression Explicit
And (Variable "x") (And (Variable "y") (Literal False))
> and (var "x") (and (var "y") false) :: Expression Simplified
Literal False
您可能希望稍后添加更多規則; 例如,也許你想要:
and (Variable x) (Not (Variable y)) | x == y = false
and (Not (Variable x)) (Variable y) | x == y = false
不得不重復兩種模式的“順序”有點煩人。 我們應該抽象一下! 數據聲明和類將是相同的,但我們會添加輔助功能eitherOrder
中的定義,並用它and
和or
。 這是使用這個想法(以及我們最終版本的模塊)的更完整的簡化集:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
module Simple (Format(..), true, false, var, not, or, and) where
import Data.Maybe
import Data.Monoid
import Prelude hiding (not, or, and)
import Control.Applicative ((<|>))
data Format = Explicit | Simplified
data Expression (a :: Format)
= Literal Bool
| Variable String
| Not (Expression a)
| Or (Expression a) (Expression a)
| And (Expression a) (Expression a)
deriving (Eq, Ord, Read, Show)
class Expr e where
true, false :: e
var :: String -> e
not :: e -> e
or, and :: e -> e -> e
instance Expr (Expression Explicit) where
true = Literal True
false = Literal False
var = Variable
not = Not
or = Or
and = And
eitherOrder :: (e -> e -> e)
-> (e -> e -> Maybe e)
-> e -> e -> e
eitherOrder fExplicit fSimplified x y = fromMaybe
(fExplicit x y)
(fSimplified x y <|> fSimplified y x)
instance Expr (Expression Simplified) where
true = Literal True
false = Literal False
var = Variable
not (Literal True) = false
not (Literal False) = true
not (Not x) = x
not x = Not x
or = eitherOrder Or go where
go (Literal True) _ = Just true
go (Literal False) x = Just x
go (Variable x) (Variable y) | x == y = Just (var x)
go (Variable x) (Not (Variable y)) | x == y = Just true
go _ _ = Nothing
and = eitherOrder And go where
go (Literal True) x = Just x
go (Literal False) _ = Just false
go (Variable x) (Variable y) | x == y = Just (var x)
go (Variable x) (Not (Variable y)) | x == y = Just false
go _ _ = Nothing
現在在ghci中我們可以做更復雜的簡化,例如:
> and (not (not (var "x"))) (var "x") :: Expression Simplified
Variable "x"
即使我們只寫了一個重寫規則的順序,兩個命令都正常工作:
> and (not (var "x")) (var "x") :: Expression Simplified
Literal False
> and (var "x") (not (var "x")) :: Expression Simplified
Literal False
我認為愛因斯坦說:“盡可能地簡化,但不能再簡化。” 你有一個復雜的數據類型和相應復雜的概念,所以我認為任何技術只能對手頭的問題更加清晰。
也就是說,第一種選擇是使用案例結構。
simplify x = case x of
Literal _ -> x
Variable _ -> x
Not e -> simplifyNot $ simplify e
...
where
sharedFunc1 = ...
sharedFunc2 = ...
這具有包括共享功能的額外好處,所述共享功能可用於所有情況但不能用於頂級命名空間。 我也喜歡這些案例如何從括號中解脫出來。 (另請注意,在前兩種情況下,我只返回原始術語,而不是創建新術語)。 我經常使用這種結構來打破其他簡化函數,就像Not
的情況一樣。
特別是這個問題可能有助於將Expression
放在底層fmap
函數上,這樣您就可以簡化子表達式,然后執行給定大小寫的特定組合。 它看起來如下所示:
simplify :: Expression' -> Expression'
simplify = Exp . reduce . fmap simplify . unExp
這里的步驟是將Expression'
展開到底層的functor表示中,映射底層術語的簡化,然后減少這種簡化並重新包裝到新的Expression'
{-# Language DeriveFunctor #-}
newtype Expression' = Exp { unExp :: ExpressionF Expression' }
data ExpressionF e
= Literal Bool
| Variable String
| Not e
| Or e e
| And e e
deriving (Eq,Functor)
現在,我已經將復雜性推到了reduce
函數中,它只是稍微復雜一點,因為它不必擔心首先減少子項。 但它現在只包含將一個術語與另一個術語合並的業務邏輯。
這可能是也可能不是一種很好的技術,但它可以使一些增強更容易。 例如,如果可以用您的語言形成無效表達式,則可以使用Maybe
值失敗來簡化該表達式。
simplifyMb :: Expression' -> Maybe Expression'
simplifyMb = fmap Exp . reduceMb <=< traverse simplifyMb . unExp
這里, traverse
將simplfyMb
應用於ExpressionF
,導致Maybe
子類的表達式, ExpressionF (Maybe Expression')
,然后如果任何子類是Nothing
,它將返回Nothing
,如果所有都是Just x
,它將返回Just (e::ExpressionF Expression')
。 Traverse實際上並沒有像這樣分成不同的階段,但它更容易解釋,就好像它一樣。 另請注意,您需要DeriveTraversable和DeriveFoldable的語言編譯指示,以及ExpressionF
數據類型的派生語句。
不足之處? 好吧,對於其中一個,你的代碼的污垢將隨處可見一堆Exp
包裝器。 考慮以下簡單術語的simplfyMb
的應用:
simplifyMb (Exp $ Not (Exp $ Literal True))
如果您了解上面的traverse
和fmap
模式,您可以在很多地方重復使用它,這樣做很好。 我也相信以這種方式定義簡化使得它對於特定的ExpressionF
結構可能變得更加健壯。 它沒有提到它們,所以深度簡化不會受到重構的影響。 另一方面,reduce函數將是。
繼續使用Binary Op Expression Expression
想法,我們可以得到以下數據類型:
data Expression
= Literal Bool
| Variable String
| Not Expression
| Binary Op Expression Expression
deriving Eq
data Op = Or | And deriving Eq
並輔助功能
{-# LANGUAGE ViewPatterns #-}
simplifyBinary :: Op -> Expression -> Expression -> Expression
simplifyBinary binop (simplify -> leftexp) (simplify -> rightexp) =
case oneway binop leftexp rightexp ++ oneway binop rightexp leftexp of
simplified : _ -> simplified
[] -> Binary binop leftexp rightexp
where
oneway :: Op -> Expression -> Expression -> [Expression]
oneway And (Literal False) _ = [Literal False]
oneway Or (Literal True) _ = [Literal True]
-- more cases here
oneway _ _ _ = []
這個想法是,你干脆把簡化案件oneway
然后simplifyBinary
將采取扭轉參數的照顧,以避免寫對稱的情況。
您可以為所有二進制操作編寫通用的簡化器:
simplifyBinWith :: (Bool -> Bool -> Bool) -- the boolean operation
-> (Expression -> Expression -> Expression) -- the constructor
-> Expression -> Expression -- the two operands
-> Expression) -- the simplified result
simplifyBinWith op cons a b = case (simplify a, simplify b) of
(Literal x, Literal y) -> Literal (op x y)
(Literal x, b') -> tryAll (x `op`) b'
(a', Literal y) -> tryAll (`op` y) a'
(a', b') -> cons a' b'
where
tryAll f term = case (f True, f False) of -- what would f do if term was true of false
(True, True) -> Literal True
(True, False) -> term
(False, True) -> Not term
(False, False) -> Literal False
這樣,你的simplify
功能就會變成
simplify :: Expression -> Expression
simplify (Not e) = case simplify e of
(Literal b) -> Literal (not b)
e' -> Not e'
simplify (And a b) = simplifyBinWith (&&) And a b
simplify (Or a b) = simplifyBinWith (||) Or a b
simplify t = t
並且可以很容易地擴展到更多的二進制操作。 它也將與正常工作Binary Op Expression Expression
的想法,你會經過Op
,而不是Expression
構造函數simplifyBinWith
和模式simplify
可以推廣:
simplify :: Expression -> Expression
simplify (Not e) = case simplify e of
(Literal b) -> Literal (not b)
e' -> Not e'
simplify (Binary op a b) = simplifyBinWith (case op of
And -> (&&)
Or -> (||)
Xor -> (/=)
Implies -> (<=)
Equals -> (==)
…
) op a b
simplify t = t
where
simplifyBinWith f op a b = case (simplify a, simplify b) of
(Literal x, Literal y) -> Literal (f x y)
…
(a', b') -> Binary op a' b'
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.