Joh*_*n L 18
我先回答你的第二个问题.实际上有许多方法可以处理Haskell(和其他FP语言)中的可变状态.首先,Haskell确实支持IO中的可变状态,通过IORef和mvar构造.使用这些对于命令式语言的程序员来说非常熟悉.也有专门的版本,如STRef和TMVar,以及可变数组,指针,以及其他各种可变数据.最大的缺点是这些通常只能在IO或更专业的monad中使用.
在函数式语言中模拟状态的最常用方法是将state作为函数参数和返回值显式传递.例如:
randomGen :: Seed -> (Int, Seed)
Run Code Online (Sandbox Code Playgroud)
这里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
Run Code Online (Sandbox Code Playgroud)
现在,任何需要PRNG的函数都可以在Random monad中运行,以便根据需要请求它们.您只需要提供初始状态和计算.
runRandomComputation :: Random a -> Seed -> a
runRandomComputation = evalState
Run Code Online (Sandbox Code Playgroud)
(注意有些函数会大大缩短randomGen2的定义;我选择了最明确的版本).
如果您的随机计算也需要访问IO,那么您使用State的monad转换器版本,StateT.
特别值得注意的是STmonad,它实质上提供了一种机制,可以将IO特定的突变封装在IO的其余部分之外.ST monad提供STRefs,它是对数据的可变引用,也是可变数组.使用ST,可以定义这样的事情:
randomList :: Seed -> [Int]
Run Code Online (Sandbox Code Playgroud)
其中[Int]是一个无限的随机数列表(它最终会根据你的PSRG循环)来自你给它的起始种子.
最后,还有功能反应式编程.目前最着名的图书馆可能是Yampa和Reactive,但其他人也值得关注.在FRP的各种实现中有几种可变状态的方法; 从我对它们的轻微使用来看,它们在概念上似乎与QT或Gtk +中的信令框架类似(例如,为事件添加侦听器).
现在,对于第一个问题.对我来说,最大的优点是可变状态与类型级别的其他代码分开.这意味着代码不能意外地修改状态,除非在类型签名中明确提到它.它还可以很好地控制只读状态与可变状态(Reader monad vs. State monad).我发现以这种方式构造我的代码非常有用,如果函数可能意外地突变状态,那么能够从类型签名中告诉它是有用的.
我个人对在Haskell中使用可变状态没有任何保留意见.最大的困难在于将状态添加到以前不需要它的东西上可能会很乏味,但在我用于类似任务的其他语言(C#,Python)中,同样的事情会很乏味.
Pau*_*aul 11
虽然我不怀疑人们会回答"使用状态monad",但我想指出另一个有用的方法:功能反应式编程(与Yampa或其他方式).