この記事はQiitaからの移転記事です。
大学の講義で LK を習ったので、定理証明支援系を書こうと思った。 その前段階として、取り敢えず論理式に推論規則を適用する仕組みを作ることにした。(後はひっくりかえすだけ)
折角なので、推論規則を書いたら勝手に函数を生成してくれる準クォートを定義してそいつをつかってみた。 こんな感じに書ける。
{-# OPTIONS_GHC -fwarn-incomplete-patterns -fwarn-unused-imports #-}
module LKRules where
import Data.List
import LKDataTypes
import LKMacros
type Rule = [Sequent] -> Maybe Sequent
isInitial :: Sequent -> Bool
= a == b
isInitial [lkseq| a |- b |]
[rule|
# Cut
Γ |- Δ, A A, Σ |- Π
--------------------------- cut
Γ, Σ |- Δ, Π
# Contract
A, A, Γ |- Δ
--------------- contractL
A, Γ |- Δ
Γ |- Δ, A, A
--------------- contractR
Γ |- Δ, A
# Weakening
Γ ├ Δ
------------- weakenL
A, Γ ├ Δ
Γ ├ Δ
------------- weakenR
Γ ├ Δ, A
# Rules for AND
Γ ├ Δ, A Σ ├ Π, B
-------------------------- andRight
Γ, Σ ├ Δ, Π, A ∧ B
A, Γ ├ Δ
------------------ andLeftR
A ∧ B, Γ ├ Δ
A, Γ ├ Δ
------------------ andLeftL
B ∧ A, Γ ├ Δ
# Rules for OR
A, Γ ├ Δ B, Σ |- Π
-------------------------- orLeft
A ∨ B, Γ, Σ |- Δ, Π
Γ ├ Δ, A
---------------- orRightR
Γ ├ Δ, A ∨ B
Γ ├ Δ, A
---------------- orRightL
Γ ├ Δ, B ∨ A
# Rules for Implies
A, Γ ├ Δ, B
-------------------------- implRight
Γ ├ Δ, A → B
Γ ├ Δ, A B, Σ ├ Π
--------------------------- implLeft
A → B , Γ, Σ ├ Δ, Π
# Rules for Not
A, Γ ├ Δ
------------------ notRight
Γ ├ Δ, ¬ A
Γ ├ Δ, A
------------------ notLeft
¬ A, Γ ├ Δ
|]
=
swap s1 s2 ss let [start, end] = sort [s1, s2]
:rs) = splitAt start ss
(ls, a:rs') = splitAt (start-end+1) rs
(ls', bin ls ++ b : ls' ++ a : rs'
permutationL :: Int -> Int -> Rule
:|- ds ] | max s1 s2 < length ss = Just $ swap s1 s2 ss :|- ds
permutationL s1 s2 [ ss = Nothing
permutationL _ _ _
permutationR :: Int -> Int -> Rule
:|- ds ] | max s1 s2 < length ds = Just $ ss :|- reverse (swap s1 s2 $ reverse ds)
permutationR s1 s2 [ ss = Nothing permutationR _ _ _
流石に可換法則はそのまま書くのはむずかしそうなのでやめた。 準クォートにしておくと、今度は逆向きのを自動生成する機能を付けたときに Rules を一切弄らなくてすむので楽だ。 上式にない変数が下式に出て来たら、引数として外側に括り出すようにする処理とかもやっている。あと、上式に同じ文字列が出て来たらガード節をくっつけてそいつらの値が一致する、と云う条件も入れるようにしている。
GHCi で動かしてみるとこんな感じになる。
> contractL [seqs| A ∧ A, A ∧ A ├ A |]
ghciJust (A ∧ A |- A)
> weakenL [lkf| A |] [seqs| C |- B |]
ghciJust (A, C |- B)
> weakenR [lkf| A |] [seqs| C |- B |]
ghciJust (C |- B, A)
例として二重否定除去を示す。
> [seqs| A |- A |]
ghciA |- A]
[
> maybeToList $ notRight it
ghci|- A, ¬A]
[
> maybeToList $ notLeft it
ghciA |- A]
[¬¬
> maybeToList $ implRight it
ghci|- ¬¬A → A] [
残りのソースは以下の通り。パーザ周りにまだ繰り返しているコードがあるのでそこを今度何とかする。
{-# LANGUAGE TemplateHaskell, ViewPatterns #-}
module LKMacros where
import LKDataTypes
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Data.Char
import Data.Generics
import Control.Applicative
import Text.Parsec
import Data.List
import Data.Foldable (foldrM)
import Control.Monad.State
import qualified Data.Map as M
lkseq :: QuasiQuoter
= QuasiQuoter { quoteType = undefined
lkseq = undefined
, quoteDec = lkSeqExp
, quoteExp = lkSeqPat
, quotePat
}
= dataToExpQ (mkQ Nothing anti) $ parseSequent str
lkSeqExp str where
anti :: [Formula] -> Maybe ExpQ
= Just [| concat $(listE $ map (appE [| toFormulaList |] . dataToExpQ (mkQ Nothing trans)) gs) |]
anti gs Var s) | isFree s = Just $ varE (mkName s)
trans (Var (c:cs))
trans (| Just prfx <- lookup c greeks = Just $ varE $ mkName $ prfx:"s"
= Nothing
trans _ = dataToPatQ (mkQ Nothing anti) $ parseSequent str
lkSeqPat str where
anti :: [Formula] -> Maybe PatQ
= Just $ foldr1 (\a b -> infixP a ('(:)) b) $ map (dataToPatQ (mkQ Nothing trans)) fs
anti fs Var str) | isFree str = Just $ varP $ mkName str
trans (Var (c:cs))
trans (| Just prfx <- lookup c greeks = Just $ varP $ mkName $ prfx:"s"
= Nothing
trans _
seqs :: QuasiQuoter
= QuasiQuoter { quoteType = undefined
seqs = undefined
, quoteDec = seqsExp
, quoteExp = seqsPat
, quotePat
}
= not (null s) && isLower (head s)
isFree s
= zip "ΓΔΣΠΘΛΞΦΩ" "gdsptlxfw"
greeks
= dataToExpQ (mkQ Nothing anti) $ run (sequent `sepBy` spaces) str
seqsExp str where
anti :: [Formula] -> Maybe ExpQ
= Just [| concat $(listE $ map (appE [| toFormulaList |] . dataToExpQ (mkQ Nothing trans)) gs) |]
anti gs Var s) | isFree s = Just $ varE (mkName s)
trans (Var (c:cs))
trans (| Just prfx <- lookup c greeks = Just $ varE $ mkName $ prfx:"s"
= Nothing
trans _ = dataToPatQ (mkQ Nothing anti) $ run(sequent `sepBy` spaces) str
seqsPat str where
anti :: [Formula] -> Maybe PatQ
= Just $ foldr1 (\a b -> infixP a ('(:)) b) $ map (dataToPatQ (mkQ Nothing trans)) fs
anti fs Var str) | isFree str = Just $ varP $ mkName str
trans (Var (c:cs))
trans (| Just prfx <- lookup c greeks = Just $ varP $ mkName $ prfx:"s"
= Nothing
trans _
lkf :: QuasiQuoter
= QuasiQuoter { quoteType = undefined
lkf = undefined
, quoteDec = formulaExp
, quoteExp = undefined
, quotePat
}
= dataToExpQ (const Nothing) $ parseFormula str
formulaExp str
rule :: QuasiQuoter
= QuasiQuoter { quoteType = undefined
rule = undefined
, quoteExp = undefined
, quotePat = ruleDec
, quoteDec
}
= do
ruleDec str let rules = run (many1 deducRule) str
concat <$> mapM build rules
where
= [t| $(a) -> $(b) |]
arrow a b = do
build (from, name, to) <- runStateT (everywhereM (mkM renamer) from) M.empty
(from', dic) pattern <- listP $ map (dataToPatQ (mkQ Nothing pat)) from'
<- dataToExpQ (mkQ Nothing expq) to
body let pnames = everything (++) (mkQ [] extractName) from
= everything (++) (mkQ [] extractName) to
enames = enames \\ pnames
unknown = patG $ map (noBindS . appE [| \xs -> and $ zipWith (==) xs (tail xs) |] . listE . map (varE . mkName . normalizeName) . snd) $ filter (not. null . drop 1 . snd) $ M.toList dic
guards <- sigD (mkName name) $ foldr arrow [t| [Sequent] -> Maybe Sequent |] $
sig map (const [t| Formula |]) unknown
<- funD (mkName name) [ clause (map (varP . mkName) unknown++[return pattern])
fun $ appE [|Just|] (return body)])
(guardedB [liftM2 (,) guards
[]:map (const wildP) unknown) (normalB [|Nothing|]) []
, clause (wildP
]return [sig, fun]
expq :: [Formula] -> Maybe ExpQ
= Just [| concat $(listE $ map (appE [| toFormulaList |] . dataToExpQ (mkQ Nothing $ trans varE)) gs) |]
expq gs pat :: [Formula] -> Maybe PatQ
= Just $ foldr1 (\a b -> infixP a ('(:)) b) $
pat fs map (dataToPatQ (mkQ Nothing $ trans varP)) fs
Var str) = Just $ var $ mkName $ normalizeName str
trans var (= Nothing
trans _ _
normalizeName [c]| Just prfx <- lookup c greeks = prfx : "s"
= map toLower xs
normalizeName xs Var v) = [normalizeName v]
extractName (= []
extractName _ Var name) = do
renamer (<- get
dic case M.lookup name dic of
Nothing -> do
$ M.insert name [name] dic
put return $ Var name
Just xs -> do
<- show <$> lift (newName name)
name' $ M.insert name (name' : xs) dic
put return $ Var name'
{-# LANGUAGE DeriveDataTypeable, RecordWildCards, FlexibleInstances #-}
module LKDataTypes where
import Text.Parsec.Expr
import Text.Parsec
import qualified Text.Parsec.Token as T
import Text.Parsec.String
import Data.List
import Control.Applicative hiding (many, (<|>))
import Data.List
import Data.Either
import Data.Data (Typeable, Data)
6 :|-
infix infixr 7 :->:
infixl 8 :\/:
infixl 9 :/\:
data Formula = Var String
| Not Formula
| Formula :\/: Formula
| Formula :->: Formula
| Formula :/\: Formula
deriving (Read, Eq, Ord, Data, Typeable)
data Sequent = (:|-) { lefts :: [Formula], rights :: [Formula] }
deriving (Eq, Ord, Data, Typeable)
instance Show Formula where
showsPrec _ (Var v) = showString v
showsPrec d (Not f) = showString "¬" . showsPrec 11 f
showsPrec d (l :\/: r) = showParen (d > 7) $ showsPrec 8 l . showString " ∨ " . showsPrec 8 r
showsPrec d (l :->: r) = showParen (d > 6) $ showsPrec 7 l . showString " → " . showsPrec 7 r
showsPrec d (l :/\: r) = showParen (d > 8) $ showsPrec 9 l . showString " ∧ " . showsPrec 9 r
instance Show Sequent where
showsPrec d (l :|- r) = showParen (d > 10) $ showsFs l . showString " |- " . showsFs (reverse r)
where
= foldr (.) id $ intersperse (showString ", ") $ map shows fs
showsFs fs
= oneOf "ΓΔΣΠΘΛΞΦΨΩ"
greek
lang :: T.LanguageDef ()
= T.LanguageDef { T.commentStart = "{-"
lang = "-}"
, T.commentEnd = "#"
, T.commentLine = True
, T.nestedComments = letter <|> greek
, T.identStart = alphaNum <|> greek <|> char '\''
, T.identLetter = empty
, T.opStart = empty
, T.opLetter = []
, T.reservedNames = ["~", "->", "\\/", "/\\", "⊃", "|-"
, T.reservedOpNames "¬", "→", "∧", "∨", "^", "v", "├"
,
]= True
, T.caseSensitive
}
T.TokenParser {..} = T.makeTokenParser lang
formula :: Parser Formula
= buildExpressionParser table term
formula <?> "formula"
= parens formula
term <|> Var <$> identifier
= [ [ Prefix $ Not <$ choice (map reservedOp ["~", "¬"])]
table Infix ((:/\:) <$ choice (map reservedOp ["∧", "/\\", "^"])) AssocLeft ]
, [ Infix ((:\/:) <$ choice (map reservedOp ["∨", "\\/", "v"])) AssocLeft ]
, [ Infix ((:->:) <$ choice (map reservedOp ["→", "->", "⊃"])) AssocRight ]
, [
]
= (:|-) <$> fs <* (choice $ map reservedOp ["|-", "├"])
sequent <*> (reverse <$> fs)
where
= formula `sepBy` comma
fs
= run sequent
parseSequent = run formula
parseFormula
=
run p src case parse (whiteSpace *> p <* eof) "" src of
Left err -> error $ show err
Right ans -> ans
class FormulaListable a where
toFormulaList :: a -> [Formula]
instance FormulaListable Formula where
= pure
toFormulaList
instance FormulaListable [Formula] where
= id
toFormulaList
= (,,) <$> (sequent `sepBy` whiteSpace)
deducRule <* lexeme (skipMany1 (char '-'))
<*> identifier
<*> sequent