私はparsecを使用して、次のパーサを書くことをしようとしている:ヒープ使用Haskellのパーセク
manyLength
:: forall s u m a.
Monad m
=> ParsecT s u m a -> ParsecT s u m Int
manyLength p = go 0
where
go :: Int -> ParsecT s u m Int
go !i = (p *> go (i + 1)) <|> pure i
これは代わり[a]
、それ リターンを返す、many
機能のようなものです回数Parser a
が成功します。
これは動作しますが、一定のヒープ領域で実行することはできません。 go
への再帰呼び出しがテールコール位置にないため、これは の意味になります。
parsecがコンストラクタをParsecT
にエクスポートすると、 はCPS'ed形式のmanyLength
を書き換えることができます。一定のヒープ領域に実行を行い
manyLengthCPS :: forall s u m a. ParsecT s u m a -> ParsecT s u m Int
manyLengthCPS p = ParsecT f
where
f
:: forall b.
State s u
-> (Int -> State s u -> ParseError -> m b) -- consumed ok
-> (ParseError -> m b) -- consumed err
-> (Int -> State s u -> ParseError -> m b) -- empty ok
-> (ParseError -> m b) -- empty err
-> m b
f s cok cerr eok _ =
let walk :: Int -> a -> State s u -> ParseError -> m b
walk !i _ s' _ =
unParser p s'
(walk $ i + 1) -- consumed-ok
cerr -- consumed-err
manyLengthCPSErr -- empty-ok
(\e -> cok (i + 1) s' e) -- empty-err
in unParser p s (walk 0) cerr manyLengthCPSErr (\e -> eok 0 s e)
{-# INLINE f #-}
manyLengthCPSErr :: Monad m => m a
manyLengthCPSErr =
fail "manyLengthCPS can't be used on parser that accepts empty input"
このmanyLengthCPS
機能:これはmanyAccum
機能と非常に似ています。ここで
はちょうど完全性についてコンストラクタです:
newtype ParsecT s u m a = ParsecT
{ unParser
:: forall b .
State s u
-> (a -> State s u -> ParseError -> m b) -- consumed ok
-> (ParseError -> m b) -- consumed err
-> (a -> State s u -> ParseError -> m b) -- empty ok
-> (ParseError -> m b) -- empty err
-> m b
}
私も 低レベルmkPT
機能を使用して、非CPS'ed機能に直接manyLengthCPS
をオンにしてみました:
manyLengthLowLevel
:: forall s u m a.
Monad m
=> ParsecT s u m a -> ParsecT s u m Int
manyLengthLowLevel p = mkPT f
where
f :: State s u -> m (Consumed (m (Reply s u Int)))
f parseState = do
consumed <- runParsecT p parseState
case consumed of
Empty mReply -> do
reply <- mReply
case reply of
Ok _ _ _ -> manyLengthErr
Error parseErr -> pure . Empty . pure $ Ok 0 parseState parseErr
Consumed mReply -> do
reply <- mReply
case reply of
Ok a newState parseErr -> walk 0 a newState parseErr
Error parseErr -> pure . Consumed . pure $ Error parseErr
where
walk
:: Int
-> a
-> State s u
-> ParseError
-> m (Consumed (m (Reply s u Int)))
walk !i _ parseState' _ = do
consumed <- runParsecT p parseState'
case consumed of
Empty mReply -> do
reply <- mReply
case reply of
Ok _ _ _ -> manyLengthErr
Error parseErr ->
pure . Consumed . pure $ Ok (i + 1) parseState' parseErr
Consumed mReply -> do
reply <- mReply
case reply of
Ok a newState parseErr -> walk (i + 1) a newState parseErr
Error parseErr -> pure . Consumed . pure $ Error parseErr
manyLengthErr :: Monad m => m a
manyLengthErr =
fail "manyLengthLowLevel can't be used on parser that accepts empty input"
manyLength
のように、manyLengthLowLevel
は一定のヒープ領域では実行されません。
それも CPS-スタイルでそれを記述することなく、それが一定のヒープ空間で実行manyLength
を書くことは可能ですか?そうでない場合は、どうしてですか? CPS形式では可能ですが非CPS形式では不可能な理由がありますか?
'行く私は=(Pの*>純粋な真)<|>純粋な虚偽>> = \成功とdesurgarすべきgo''のように思える! - >成功はその後、行けば(i + 1)の他に、純粋なi' 。これは、バインド演算子への引数で使用されているので、 'go'がまだテールコール位置にないように見えます。これはどのように機能しますか? – illabout
詳細についてはわかりませんが、おおまかな考えでは、 '<|>'は 'ピュアi'という連続を付けています。左オペランドが入力を消費するとすぐに破棄されるのではなく、' >> = '、右オペランドがブール値に適用されると、直ちに' go(i + 1) 'または' pure i'のいずれかに還元されます。 –