私は実際に問題を逆方向に実行します: モナド解法から始めて、それから手巻き解法まで逆方向に作業します。これにより、手動で正しいソリューションにたどり着いた場合と同じコードが生成されます。
モナド パーサーの典型的な型シグネチャは次の形式です。
type Parser a = String -> Maybe (a, String)
String
の外側にファイナルがあるフォームとのわずかな違いに注意してくださいMaybe
。String
どちらも有効ですが、解析が失敗した場合、残り物は無効であると考えるので、私はこの形式をより好みます。
このタイプは、実際には の特殊なケースであり、次のStateT
ように定義されています。
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
選択した場合、次のことに注意してください。
s = String
m = Maybe
...Parser
タイプを返します:
type Parser a = StateT String Maybe a
-- or: type Parser = StateT String Maybe
これの優れている点は、1 つの文字を取得するパーサーを手動で 1 つ定義するだけでよいことです。
anyChar :: Parser Char
anyChar = StateT $ \str -> case str of
[] -> Nothing
c:cs -> Just (c, cs)
StateT
ラッパーを削除すると、のタイプは次のようにanyChar
なることに注意してください。
anyChar :: String -> Maybe (Char, String)
ラップすると、次のStateT
ようになります。
anyChar :: StateT String Maybe Char
...これはただParser Char
です。
プリミティブ パーサーを取得したら、StateT
のMonad
インターフェイスを使用して他のすべてのパーサーを定義できます。たとえば、単一の文字に一致するパーサーを定義しましょう。
import Control.Monad
char :: Char -> Parser ()
char c' = do
c <- anyChar
guard (c == c')
それは簡単でした! モナドguard
のインスタンスが必要ですが、すでに存在しています。MonadPlus
その理由は、次の 2 つのMonadPlus
インスタンスのおかげです。
instance (MonadPlus m) => MonadPlus (StateT s m) where ...
instance MonadPlus Maybe where ...
これら 2 つのインスタンスの組み合わせは、 がStateT s Maybe
自動的に を実装するMonadPlus
ことを意味しますguard
。
これら 2 つのパーサーがあれば、最終的なパーサーは非常に簡単に記述できます。
data Wff = Or Char Char deriving (Show)
parseWff :: Parser Wff
parseWff = do
char '['
a <- anyChar
char '|'
b <- anyChar
char ']'
return (Or a b)
その方がはるかに明確で、何が起こっているのかを理解するのが簡単です。それも機能します:
>>> runStateT parseWff "[A|B]"
Just (Or 'A' 'B',"")
逆算
これは、元の質問につながります: 同じ動作をどのように手書きしますか? インスタンスからさかのぼって、Monad
内部MonadPlus
で何をしているのかを推測します。
これを行うには、基底モナドが であるときに、Monad
とのMonadPlus
インスタンスが何に還元されるかを推測する必要があります。のインスタンスから始めましょう:StateT
Maybe
Monad
StateT
instance (Monad m) => Monad (StateT s m) where
return r = StateT (\s -> return (r, s))
m >>= f = StateT $ \s0 -> do
(a, s1) <- runStateT m s0
runStateT (f a) s1
基本モナドの一般的な用語で定義されていることに注意してください。これは、上記のコードが行うことを導出するMonad
ために のインスタンスも必要であることを意味します。Maybe
instance Monad Maybe where
return = Just
m >>= f = case m of
Nothing -> Nothing
Just a -> f a
Maybe
モナドインスタンスをモナドインスタンスに置き換えると、次のStateT
ようになります:
instance Monad (StateT s Maybe) where
return r = StateT (\s -> Just (r, s))
m >>= f = StateT $ \s0 -> case runStateT m s0 of
Nothing -> Nothing
Just (a, s1) -> runStateT (f a) s1
Monad
のインスタンスを導出するために同じことを行うことができますStateT s Maybe
。と`たぶんMonadPlus
:StateT
instance (MonadPlus m) => MonadPlus (StateT s m) where
mzero = StateT (\_ -> mzero)
mplus (StateT f) (StateT g) = StateT (\s -> mplus (f s) (g s))
instance MonadPlus Maybe where
mzero = Nothing
mplus m1 m2 = case m1 of
Just a -> Just a
Nothing -> case m2 of
Just b -> Just b
Nothing -> Nothing
...そしてそれらを1つに結合します:
instance MonadPlus (StateT s Maybe) where
mzero = StateT (\_ -> Nothing)
mplus (StateT f) (StateT g) = StateT $ \s -> case f s of
Just a -> Just a
Nothing -> case g s of
Just b -> Just b
Nothing -> Nothing
これで、パーサーが内部で何をしているかを導き出すことができます。char
パーサーから始めましょう。
char c' = do
c <- anyChar
guard (c == c')
これにより、次の脱糖が行われます。
char c' = anyChar >>= \c -> guard (c == c')
(>>=)
forの機能を導き出したStateT s Maybe
ので、それを次のように置き換えます。
char c' = StateT $ \str0 -> case runStateT anyChar str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
の定義はすでにわかっているanyChar
ので、次のように置き換えてみましょう。
char c' = StateT $ \str0 -> case runStateT (StateT $ \str -> case str of
[] -> Nothing
c:cs -> Just (c, cs) ) str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
runStateT
が の逆であることもわかっているStateT
ので、次のようになります。
char c' = StateT $ \str0 -> case (\str -> case str of
[] -> Nothing
c:cs -> Just (c, cs) ) str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
次に、ラムダを次のように適用できますstr0
。
char c' = StateT $ \str0 -> case (case str0 of
[] -> Nothing
c:cs -> Just (c, cs) ) of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
ここで、外側の case ステートメントを内側の case ステートメントに分散させます。
char c' = StateT $ \str0 -> case str0 of
[] -> case Nothing of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
c:cs -> case Just (c, cs) of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
... そして case ステートメントを評価します。
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT ((\c -> guard (c == c')) c) cs
次に、ラムダをc
次のように適用できます。
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT (guard (c == c')) cs
それをさらに単純化するには、何が機能するかを知る必要がguard
あります。そのソースコードは次のとおりです。
guard pred = if pred then return () else mzero
何が何であるかはすでにわかっているreturn
ので、それらを次のように置き換えましょう。mzero
StateT s Maybe
guard pred = if pred then StateT (\s -> Just ((), s)) else StateT (\_ -> Nothing)
これを関数にインライン化できます。
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT (if (c == c')
then StateT (\s -> Just ((), s))
else StateT (\_ -> Nothing) ) cs
それを分配runStateT
すると、次のようになります。
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> (if (c == c')
then (\s -> Just ((), s))
else (\_ -> Nothing) ) cs
同様に、両方のブランチを に適用できますcs
。
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> if (c == c') then Just ((), cs) else Nothing
Monad
これは、またはMonadPlus
インスタンスをまったく使用していなければ、手動で記述した同等のコードです。
最後のパーサー
最後の関数についてもこのプロセスを繰り返しますが、導出は演習として残します。
parseWff = do
char '['
a <- anyChar
char '|'
b <- anyChar
char ']'
return (Or a b)
parseWff = StateT $ \str0 -> case str0 of
[] -> Nothing
c1:str1 -> if (c1 == '[')
then case str1 of
[] -> Nothing
c2:str2 -> case str2 of
[] -> Nothing
c3:str3 -> if (c3 == '|')
then case str3 of
[] -> Nothing
c4:str4 -> case str4 of
[] -> Nothing
c5:str5 -> if (c5 == ']')
then Just (Or c2 c4, str5)
else Nothing
else Nothing
else Nothing
...しかし、それをさらに単純化して次のようにすることができます。
parseWff = StateT $ \str0 -> case str0 of
'[':c2:'|':c4:']':str5 -> Just (Or c2 c4, str5)
_ -> Nothing
tail
あなたが書いた関数とは異なり、これは部分的な関数や不完全なパターン マッチを使用していないことに注意してください。また、あなたが書いたコードはコンパイルされませんが、コンパイルされたとしても、依然として間違った動作をします。