【问题标题】:Mixing IO with State computation混合 IO 和状态计算
【发布时间】: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] IO monad,并在同一个 do-block 中执行有状态操作和 IO 操作。
  • 不过,如果你在文件顶部添加{-# LANGUAGE FlexibleContexts #-},然后将addEntry的类型签名更改为MonadState [Int] m =&gt; Int -&gt; 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


【解决方案1】:

我不确定您是否正在使用 state,因为您想尝试一下,但是您可以在没有 state monad 的“麻烦”的情况下实现 state 本身。

module Main where

addEntry :: Int -> [Int] -> [Int]
addEntry = (:)

mainLoop :: [Int] -> IO ()
mainLoop entries = do
  entry <- readLn
  let newEntries = addEntry entry entries
  print newEntries
  mainLoop newEntries

main :: IO ()
main = mainLoop []

【讨论】:

  • 我确实想尝试一下,总的来说,我正在努力让 monads 更加舒适。但更重要的是,我想让事情尽可能简单。所以这很有帮助!
猜你喜欢
  • 2011-02-03
  • 1970-01-01
  • 2016-02-09
  • 1970-01-01
  • 1970-01-01
  • 2011-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多