【发布时间】:2014-10-01 14:47:47
【问题描述】:
作为一个练习,我正在用 Haskell 编写一个命令行 RPN 计算器。这个想法是它将提示输入(数字或运算符)并打印出新堆栈。我的计划是将数字列表存储在状态单子中,并针对该列表执行计算。例如:
> 4
[4]
> 3
[3,2]
> 5
[5,3,2]
> +
[8, 2]
等等。
我一开始只是尝试在 State monad 中建立列表,每个条目都有输入和输出。由于 IO 和 State 在同一个函数中的组合,我已经被卡住了。我的问题是,我还需要对输入进行递归,以在输入第一个数字后继续提示。
到目前为止,这是我的代码:
module Main where
import Control.Monad.State
addEntry :: Int -> State [Int] Int
addEntry entry = do
entries <- get
put (entry : entries)
return entry
mainLoop :: [Int] -> IO ()
mainLoop entries = do
entry <- readLn
newEntries <- execState (addEntry entry) entries
print newEntries
mainLoop newEntries
main :: IO ()
main = do
print $ mainLoop []
这是我目前遇到的编译器错误:
src/Main.hs@14:28-14:42 Couldn't match type [Int] with ‘IO [Int]’
Expected type: State (IO [Int]) Int
Actual type: State [Int] Int …
src/Main.hs@14:44-14:51 Couldn't match expected type ‘IO [Int]’ with actual type [Int] …
关于如何构造这些函数以使我不结合 IO 和状态的任何提示?
【问题讨论】:
-
您是否考虑过使用
StateT [Int] IO Int代替StateT [Int] Identity Int(与State [Int] Int相同)?然后您可以使用liftIO将 IO 操作提升到StateT [Int] IOmonad,并在同一个 do-block 中执行有状态操作和 IO 操作。 -
不过,如果你在文件顶部添加
{-# LANGUAGE FlexibleContexts #-},然后将addEntry的类型签名更改为MonadState [Int] m => Int -> m Int,然后将execState更改为@,则可以编译此文件987654335@,虽然print $ mainLoop []也有编译器错误,但应该只是mainLoop [],因为它自己进行打印并且不返回任何内容。 Like this -
在现实世界的应用程序中将 IO 包装的值实际传递到您的状态中是否很常见?除非实际的状态计算出于某种原因依赖 IO,否则我觉得它很脏。
-
monad 转换器的全部意义在于能够将不同种类的 monad 的效果组合到一个更大的“堆栈”中。您没有将 IO 包装值作为您的状态传递,而是同时执行
IO操作和StateT操作。由于您看起来想要为 RPN 计算器编写相当于 REPL 的内容,这意味着您希望在执行 IO 时跟上状态。您可以将所有实际的 RPN-ing 移至纯函数,并在需要获取值和输出值时使用StateT MyState IO。 -
执行此操作的常用方法类似于this。这区分了哪些部分可以执行 IO,哪些只能执行有状态操作。使用
MonadState上下文使这些函数更容易组合,并将IO 操作全部保存在rpnREPL
标签: haskell