![](/img/trans.png)
[英]How do I force evaluation of an IO action within `unsafePerformIO`?
[英]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 a
到Parser (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))
。由於Parser
和IO
都是monad,因此對於它們兩者,我們都join :: m (ma) -> ma
,它允許折疊Double Parser
或double IO
。但是,在我們的結果中,我們將IO
和Parser
交織了,我們需要的是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)))
,然后使用join
和liftM 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
! 結合Reader
和Maybe
方法只有一種明智的方法,因此這兩個轉換Reader
都會產生相同的結果。
幸運的是,一旦有人為我們定義了一個轉換器,我們就不必關心這些東西。 我們需要知道的是,法律掌握着,最后如何運行變壓器組。
因此,使用ParsecT su IO a
是正確的解決方案。 ParsecT
知道如何在另一個monad中交錯分析,並允許您合並兩個操作之間的操作,而不必處理內部操作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.