簡體   English   中英

如何在避免unsafePerformIO的同時嵌套解析器(IO a)?

[英]How to nest Parser (IO a) while avoiding unsafePerformIO?

在基於text-icu的BreakIterator進行解析的過程中,我一直堅持實現這樣的功能

conditionalParser :: (a -> Bool) -> Parser a -> Parser a -> Parser a -> Parser a
conditionalParser f a b c = do
        a' <- a
        if f a'
                then b
                else c

但有一種

conditionalParserIO :: (a -> Bool) -> Parser (IO a) -> Parser (IO a) -> Parser (IO a) -> Parser (IO a)

是否可以不執行unsafePerformIO

到目前為止,我只能使用一些嵌套的do ,最后返回的類型是Parser (IO (Parser (IO a))) ,但不知道如何折疊它們。

我認為您想要使用ParsecT代替Parser

conditionalParserM :: Monad m => (a -> Bool) -> ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
conditionalParserM f a b c = do
    a' <- a
    if f a' then b else c

此功能可用於所有類型的Monad ,而不僅限於IO

我想這是可以從一個轉換ParsecT su IO aParser (IO a)使用runParsecT ,這取決於Parser還是這個 ?)你使用。 但是,我建議您僅重組代碼即可使用ParsecT


澄清度

conditionalParserM不能代替conditionalParserIO 我建議您需要更改程序的工作方式,因為嘗試做自己想做的事情(沒有unsafePerformIO ,您幾乎不應該使用)是不可能的。

您正在基於IO操作的結果來構成解析器,這意味着解析器本身在運行時會產生副作用。 為了將其封裝在類型中,您需要使用monad轉換器。

因此,要使用conditionalParserM ,您需要重組代碼以與ParsecT而不是Parser

我只想評論Parsec su (IO a)ParsecT su IO a之間的區別。

您正確地觀察到,嘗試使用Parsec (IO a)實現函數會產生Parser (IO (Parser (IO a)) 。由於ParserIO都是monad,因此對於它們兩者,我們都join :: m (ma) -> ma ,它允許折疊Double Parser或double IO 。但是,在我們的結果中,我們將IOParser交織了,我們需要的是IO (Parser a) -> Parser (IO a)類型的某些功能IO (Parser a) -> Parser (IO a) 。函數f和一些x :: Parser (IO (Parser (IO a)) ,我們可以將其用作liftM fx :: Parser (Parser (IO (IO a))) ,然后使用joinliftM join折疊兩個部分到所需的Parser (IO a)

不幸的是,沒有這樣的通用功能可以交換兩個單子。 在不知道monad內部的情況下構造這樣的函數是不可能的,對於某些monad甚至根本不可能。 例如,沒有類型為(a -> Maybe b) -> Maybe (a -> b) a- (a -> Maybe b) -> Maybe (a -> b)總函數(第一個monad是Maybe ,第二個monad是讀者monad (->) a )。

這就是為什么我們有monad變壓器。 與某個單子M對應的單子變換器知道如何將M與另一個單子交織。 對於某些monad,例如Reader ,可以按上述方式將其與另一個monad交換,並且它的轉換器正是這樣做的。 ReaderT rma定義為r -> ma ,我們可以構造:

import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Reader

swapReader :: (Monad m) => m (Reader r a) -> Reader r (m a)
swapReader = fromReaderT . join . lift . liftM (toReaderT . liftM return)
  where
    -- Helpers to convert ReaderT to Reader and back
    fromReaderT :: (Monad m) => ReaderT r m a -> Reader r (m a)
    fromReaderT = reader . runReaderT
    toReaderT :: (Monad m) => Reader r (m a) -> ReaderT r m a
    toReaderT = ReaderT . runReader

通過同時擴大內部和外部部分,將m (Reader ra)轉換為ReaderT rm (ReaderT rma) ,然后使用join折疊它。

對於其他MaybeT ,例如MaybeT ,無法進行交換(如上述示例中的(->) a Monad)。 因此,它們的變壓器的定義有所不同,例如MaybeT ma定義為m (Maybe a) ,而不是Maybe (ma) 因此ReaderT r Maybe a是同構MaybeT (ReaderT r) a 結合ReaderMaybe方法只有一種明智的方法,因此這兩個轉換Reader都會產生相同的結果。

幸運的是,一旦有人為我們定義了一個轉換器,我們就不必關心這些東西。 我們需要知道的是,法律掌握着,最后如何運行變壓器組。

因此,使用ParsecT su IO a是正確的解決方案。 ParsecT知道如何在另一個monad中交錯分析,並允許您合並兩個操作之間的操作,而不必處理內部操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM