【问题标题】:Haskell and StateHaskell 和状态
【发布时间】:2011-04-26 01:01:15
【问题描述】:

Haskell 是一种纯函数式编程语言。

我的问题是: 使用 Haskell 解决涉及大量状态的问题(例如 GUI 编程或游戏编程)有哪些优点和缺点?

还有一个次要问题:有哪些方法可以以函数方式处理状态?

提前致谢。

【问题讨论】:

标签: user-interface haskell state monads


【解决方案1】:

我先回答你的第二个问题。在 Haskell(和其他 FP 语言)中,实际上有很多方法可以处理可变状态。首先,Haskell 通过IORefmvar 构造支持IO 中的可变状态。使用这些命令式语言的程序员会感到非常熟悉。还有专门的版本,例如STRefTMVar,以及可变数组、指针和其他各种可变数据。最大的缺点是这些通常只在 IO 或更专业的 monad 中可用。

在函数式语言中模拟状态的最常见方法是将状态显式传递为函数参数和返回值。例如:

randomGen :: Seed -> (Int, Seed)

这里randomGen 接受一个种子参数并返回一个新种子。每次调用它时,都需要跟踪下一次迭代的种子。这种技术始终可用于状态传递,但很快就会变得乏味。

可能最常见的 Haskell 方法是使用 monad 来封装这种状态传递。我们可以用这个替换randomGen

-- a Random monad is simply a Seed value as state
type Random a = State Seed a

randomGen2 :: Random Int
randomGen2 = do
  seed <- get
  let (x,seed') = randomGen seed
  put seed'
  return x

现在任何需要 PRNG 的函数都可以在 Random monad 中运行以根据需要请求它们。你只需要提供一个初始状态和计算。

runRandomComputation :: Random a -> Seed -> a
runRandomComputation = evalState

(注意有些函数大大缩短了 randomGen2 的定义;我选择了最明确的版本)。

如果您的随机计算还需要访问IO,那么您可以使用State 的monad 转换器版本StateT

特别值得注意的是ST monad,它本质上提供了一种机制来封装 IO 特定的突变,使其远离 IO 的其余部分。 ST monad 提供 STRefs,它是对数据的可变引用,也是可变数组。使用 ST,可以这样定义:

randomList :: Seed -> [Int]

其中 [Int] 是从您提供的起始种子开始的无限随机数列表(最终会根据您的 PSRG 循环)。

最后是Functional Reactive Programming。可能目前最突出的库是YampaReactive,但其他库也值得一看。在 FRP 的各种实现中,有几种可变状态的方法;从我对它们的轻微使用来看,它们通常在概念上类似于 QT 或 Gtk+ 中的信号框架(例如,为事件添加侦听器)。

现在,第一个问题。对我来说,最大的优势是可变状态在类型级别与其他代码分离。这意味着除非在类型签名中明确提及,否则代码不会意外修改状态。它还可以很好地控制只读状态与可变状态(Reader monad vs. State monad)。我发现以这种方式构造我的代码非常有用,并且能够仅从类型签名中判断一个函数是否会意外地改变状态是很有用的。

我个人对在 Haskell 中使用可变状态没有任何保留意见。最大的困难是向以前不需要的东西添加状态可能很乏味,但在我用于类似任务的其他语言(C#、Python)中同样的事情会很乏味。

【讨论】:

  • 这里的Seed 是什么?我尝试在我的文件顶部将其定义为 int (type Seed = Int) 但收到错误 No instance for (Show (Random Int)) (我最初使用了您的代码理论,但不起作用,因此尝试将其全部复制粘贴那么这个错误)。
  • 这更像是伪代码而不是实际的实现。 Seed 是一个抽象类型,表示 PRNG 的状态相关数据。我使用 PNRG 是因为它是一类有状态算法的著名示例。如果您需要随机数生成器,我建议您使用 mwc-randommersenne-random 之类的软件包。
  • 感谢您的回复 - 我试图真正理解 moads 而不是使用 PRNG,但我认为这是一个非常好的例子。我完全被困住了。几个小时前我确实问了一个关于这个的问题,但现在我更加困惑了! stackoverflow.com/questions/23595363/…
【解决方案2】:

虽然我不怀疑人们会回答“使用 state monad”,但我想指出另一种有用的方法:函数式反应式编程(使用 Yampa 或其他方式)。

【讨论】:

    【解决方案3】:

    使用 Haskell 解决涉及大量状态的问题(例如 GUI 编程或游戏编程)有哪些优点和缺点?

    优点是,即使您没有特别利用纯度,Haskell 也只是一种好语言

    一流的功能——在 2010 年应该没什么大不了的,但确实如此。具有模式匹配的代数类型。强大的静态类型检查和类型推断。干净的语法。一流的并发性、STM 和无线程的纯并行性。一个好的编译器。每天都有大量的图书馆等等。一个活跃、乐于助人的社区。​​p>

    这些不是像纯洁或懒惰这样的重大意识形态决定。他们只是好主意。它们是大多数语言可以拥有的东西,但太多的东西没有。

    【讨论】:

    • 你能告诉我一流并发是什么意思吗?
    【解决方案4】:

    状态单子是在 Haskell 中建模 GUI 或游戏的最糟糕的方法。我认为第二好的选择是在这两种情况下都使用并发。然而,Paul 提到了最好的选择:函数响应式编程 (FRP)。

    我个人提倡箭头化 FRP (AFRP),我认为它最初是作为 Yampa 实现的,后来被分叉为更有用的 Animas。但是,Yampa 很快就达到了极限,所以我编写了一个更强大、更具表现力的库,名为 netwire,它在概念上也比前两者有所改进。

    本质上,AFRP 是一个功能状态系统。它的功能在于状态不是建模为改变变量值,而是变异函数。这更简洁,不需要像 state monad 那样的命令式编程。

    【讨论】:

      【解决方案5】:

      通常你会做的是使用带有 StateT 和 IO 的 Monad Transformer,这是因为视图 ​​(GUI) 需要 IO 才能响应,但是一旦你在 newtype 中定义了 Monad Transformer,你会喜欢仅使用 MonadState 接口制作游戏逻辑的签名,这样您仍然可以享受非 IO-ness 更改的好处。下面的代码解释了我的意思:

      {-# LANGUAGE GeneralizedNewtypeDeriving #-}
      import Control.Monad.State
      
      data GameState = GameState { ... } deriving (Show)
      newtype GameMonad a = GameMonad (StateT GameState IO a) 
                            deriving (Monad, MonadState GameState, MonadIO)
      
      -- This way, now you have a monad with the state of the info
      -- what would you like now is being able to modify the state, without actually
      -- having IO capabilites.
      
      movePlayerOnState :: (MonadState GameState) m => Position -> m ()
      -- In this function we get the state out of the state monad, and then we modify
      -- with a pure function, then put the result back again
      
      -- Other times you would like to have the GUI API available, requiring the IO monad
      -- for that
      renderGameFromState :: MonadIO m => GameState -> m ()
      -- in this method you would use liftIO method to call the GUI API
      

      如果您不了解 monad,则此代码相当复杂,但我的经验法则是,找出 State Monad 的用途,了解 Monad Transformer 是什么(无需了解它们是如何工作的)以及如何使用 StateT monad。

      我可以向您指出我与其他队友一起做的一个推箱子项目,它可能有用,它使用 ncurses 作为 GUI,但您可以了解逻辑以及我们如何管理游戏中的状态

      http://github.com/roman/HaskBan

      祝你好运。

      【讨论】:

        猜你喜欢
        • 2014-08-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-04
        • 2020-03-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多