User interface 哈斯凯尔州
Haskell是一种纯函数式编程语言 我的问题是: 使用Haskell解决涉及大量状态的问题(例如GUI编程或游戏编程)有哪些优点和缺点? 还有第二个问题:有什么方法可以用函数的方式处理状态User interface 哈斯凯尔州,user-interface,haskell,state,monads,User Interface,Haskell,State,Monads,Haskell是一种纯函数式编程语言 我的问题是: 使用Haskell解决涉及大量状态的问题(例如GUI编程或游戏编程)有哪些优点和缺点? 还有第二个问题:有什么方法可以用函数的方式处理状态 提前感谢。虽然我不怀疑人们会回答“使用状态单子”,但我想指出另一种有用的方法:函数式反应式编程(使用Yampa或其他方式) Yampa拱廊: FRP GUI工具包: 其他Yampa资源: 我先回答你的第二个问题。在Haskell(和其他FP语言)中,实际上有很多方法可以处理可变状态。首先,Haskell通
提前感谢。虽然我不怀疑人们会回答“使用状态单子”,但我想指出另一种有用的方法:函数式反应式编程(使用Yampa或其他方式)
- Yampa拱廊:
- FRP GUI工具包:
- 其他Yampa资源:
- 我先回答你的第二个问题。在Haskell(和其他FP语言)中,实际上有很多方法可以处理可变状态。首先,Haskell通过
IORef
和mvar
构造支持IO中的可变状态。使用这些命令式语言的程序员会感到非常熟悉。还有专门的版本,如STRef
和TMVar
,以及可变数组、指针和各种其他可变数据。最大的缺点是,它们通常只在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
(注意,有些函数大大缩短了randomGen2的定义;我选择了最明确的版本)
如果您的随机计算还需要访问IO
,则使用状态的monad transformer版本,StateT
特别值得注意的是ST
monad,它本质上提供了一种机制来封装IO特异性突变,使其远离IO的其余部分。ST monad提供STRefs,STRefs是对数据的可变引用,也提供可变数组。使用ST,可以定义如下内容:
randomList :: Seed -> [Int]
其中[Int]是一个无限的随机数列表(它最终将根据您的PSRG循环),从您给它的起始种子开始
最后,还有一个问题。目前最著名的图书馆可能是和,但其他图书馆也值得一看。在FRP的各种实现中,有几种实现可变状态的方法;从我对它们的轻微使用来看,它们在概念上通常与QT或Gtk+中的信令框架类似(例如,为事件添加侦听器)
现在,第一个问题。对我来说,最大的优势是可变状态在类型级别与其他代码分离。这意味着代码不能意外地修改状态,除非在类型签名中明确提到它。它还可以很好地控制只读状态和可变状态(Reader monad和state monad)。我发现以这种方式构造代码非常有用,并且能够从类型签名判断函数是否可能意外地改变状态非常有用
我个人对在Haskell中使用可变状态并没有任何保留。最大的困难是,将状态添加到以前不需要它的东西上可能会很乏味,但在我用于类似任务的其他语言(C#,Python)中也会很乏味。通常情况下,您会使用带有StateT和IO的Monad转换器,这是因为视图(GUI)需要IO才能响应,但是,一旦您在
newtype
中定义了Monad转换器,您希望仅使用MonadState
接口创建游戏逻辑的签名,这样您仍然可以享受非IO更改的好处。下面的代码解释了我的意思:
{-# 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转换器是什么(不需要了解它们是如何工作的)以及如何使用StateT Monad
我可以告诉你我和其他队友一起做的一个Sokoban项目可能会有用,它使用ncurses作为GUI,但是你可以了解逻辑以及我们如何管理游戏中的状态
祝你好运
使用Haskell解决涉及大量状态的问题(例如GUI编程或游戏编程)有哪些优点和缺点
优点是,即使你没有特别利用纯度,Haskell只是一种好语言
一流的功能——在2010年不应该是什么大不了的事情,但事实确实如此。具有模式匹配的代数类型。强大的静态类型检查和类型推断。干净的语法。第一类并发、STM和无线程纯并行。一个好的编译器。每天都有成千上万的图书馆。一个积极、乐于助人的社区
这些都不是像纯洁或懒惰这样的重大意识形态决定。它们只是好主意。它们是大多数语言都可以拥有的东西,但太多语言没有。在Haskell中,状态单子是最糟糕的GUI或游戏建模方式。我认为第二个最好的选择是在这两种情况下都使用并发。然而,Paul提到了最好的选择:函数式反应式编程(FRP) 就我个人而言,我提倡箭头化FRP(AFRP),我认为它最初是作为Yampa实现的,后来作为稍微更有用的Animas分叉。然而,Yampa很快就达到了极限,因此我编写了一个更强大、更具表现力的库,名为,它在概念上也比前两个库有所改进 本质上
{-# 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