我正在创建一种用于交互式日期争论的简单语言。 我想要一个代表“今天”的规则:
import Data.Time
import qualified Text.Parsec as Parsec
import Text.Parsec.String(Parser)
data Date = Date { year :: Int
, month :: Int
, day :: Int
}
parseToday :: Parser Date
parseToday = do
Parsec.string "today"
now <- getCurrentTime
let (year, month, day) = toGregorian $ utctDay now
return $ Date year month day
这不起作用,因为 getCurrentTime
返回一个 IO UTCTime
类型的 monad,而解析器的类型为 Parser Date
。
当然,获取当前时间需要是一个 IO 操作。
但是,对于我拥有的所有其他解析器,是否没有办法将所有内容都放入 IO
monad 中?
最佳答案
一种选择是制作两种类型;一个代表您可能需要解析的所有内容,另一个代表“编译”版本。
data ParsedDate = Today | Specified Date
parseToday :: Parser ParsedDate
parseToday = Today <$ Parsec.string "today"
compile :: ParsedDate -> IO Date
compile Today = do
(year, month, day) <- toGregorian . utctDay <$> getCurrentTime
return (Date year month day)
compile (Specified date) = return date
如果您有一个包含数据结构,其中可能包含许多 ParsedDate
,您可能需要确保 getCurrentTime
仅被调用一次,以便它们相关即使解析器在时钟即将从一天到下一天滴答作响时正确运行,彼此之间也会保持一致。解决这个问题的第二个选项如下所示:
parseToday :: ParserT ((->) UTCTime) Date
parseToday = do
Parsec.string "today"
(year, month, day) <- asks (toGregorian . utctDay)
return (Date year month day)
这种方法的缺点是您需要预先完成所有的IO
;如果 getCurrentTime
是您需要收集的唯一信息,那可能没什么大不了的,但如果您可能有更昂贵的 IO
操作,您只想根据需要执行,第一种方法更好。如果您同时需要一次性执行和按需执行,事情就会变得有点复杂......
关于haskell - 如何重构才不会卡在IO monad里?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69999326/