Luke (luqui) は、おそらく最も洗練された問題の考え方を提示しました。ただし、彼のエンコーディングでは、作成する書き換えルールごとに、トラバーサルの大部分を手動で取得する必要があります。
A Pattern for Most Composable Functionsの Bjorn Bringert のコンポは、これを簡単にすることができます。特に、記述する必要があるそのような正規化パスが複数ある場合はそうです。これは通常、Applicatives またはランク 2 の型で記述されますが、ここでは簡単にするために、次のことを延期します。
与えられたデータ型
data LogicalExpression
= Var Char
| Neg LogicalExpression
| Conj LogicalExpression LogicalExpression
| Disj LogicalExpression LogicalExpression
| Impl LogicalExpression LogicalExpression
deriving (Show)
非トップレベルのサブ式を探し出すために使用されるクラスを定義できます。
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 が指摘しているように、すべての否定分布を 1 回のパスで実行する方がおそらく良いため、否定された含意の正規化と二重否定の除去も含めて、否定の正規形の式を生成します (既に排除された含意)
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
重要なのは最後の行で、上記の他のケースのいずれにも一致しない場合、このルールを適用できる部分式を探しに行くことを示しています。また、 を用語にプッシュしてから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
、または書き換えルールでより複雑なアクティビティを実行する必要がある場合に役立ちます。
たとえば、通常の形式を追求するのではなく、deMorgan の法則が適用される場所のサブセットに適用したい場合などに、これを使用する必要があるかもしれません。
書き換えている関数、使用している Applicative、またはトラバーサル中の情報フローの方向性に関係なく、compos
定義はデータ型ごとに 1 回だけ指定する必要があることに注意してください。