哈斯克尔:Yesod和州

我正在阅读Toy URL Shortener的代码.但是,有一些重要的部分我无法理解.

它具有以下代码:

data URLShort = URLShort { state :: AcidState URLStore }

出于测试目的,我在自己的应用程序中写了这样的东西:

data MyApp = MyApp { state :: Int }

然后我可以通过改变来编译

main = warpDebug 3000 MyApp

main = warpDebug 3000 (MyApp 42)

然后,我可以通过执行操作来读取处理程序中的状态

mystate <- fmap state getYesod 

灵感来自酸< - fmap状态getYesod在文章中.但是,我不知道怎么写. 我也尝试过:

data MyApp = MyApp { state :: State Int Int }

但我对此并没有做多少.

我试图通过做一些简单的类似例子来弄清楚AcidState是如何工作的,并认为由于AcidState将所有内容保存在内存中,我应该能够做同样的事情吗?

对这里发生的事情的任何形式的一般性解释,以及也许我如何忽略这一点都将非常感激.

最佳答案
AcidState数据类型不是一直是不可变的值;它内部包含对可变数据的引用.在这种情况下,Yesod-land中存储的内容只是对此数据的不可变引用.更新状态时,实际上并不更新基础数据类型中的值,而是更新它指向的内存.

Haskell世界中的每个值都是不可变的.但是,Haskell领域之外的很多东西都不是一成不变的;例如,当您执行putStrLn时,终端会改变其显示以显示新内容. putStrLn操作本身是一个不可变的纯值,但它描述了如何执行涉及变异的操作.

还有其他功能也会产生执行突变的动作;如果你执行ref< - newIORef 0,你会得到一个描述创建可变内存单元格的动作的值.如果然后执行modifyIORef ref(1),则会得到一个值,该值描述将该单元格中的值递增1的操作.ref值是纯值,它只是对可变单元格的引用.代码也纯粹是功能性的,因为每个部分只描述一个动作;在Haskell程序中没有任何可变性. 这就是AcidState如何实现其状态:通过使用管理Haskell世界之外的状态的系统.这并不像C语言这样具有完全可变性“一样糟糕”,因为在Haskell中,你可以用monad的力量来控制可变性.使用AcidState是非常安全的,据我所知,不涉及使用unsafePerformIO. 在这种情况下,使用AcidState,您可以在IO monad中使用openAcidState emptyStore来创建新的酸状态(该行是描述打开新酸状态的IO操作的值).您可以使用createCheckpointAndClose来安全地将酸状态保存到磁盘.最后,使用update’函数来改变酸状态的内容. 要使用IORefs创建一个“小状态”(最简单的可变状态形式,除了可能是ST monad),你首先要在你的基础数据类型中添加这样的字段:

data VisitorCounter = VisitorCounter { visitorCounter :: IORef Int }

然后你做:

main = do
  counter <- newIORef 0
  warpDebug 3000 (VisitorCounter counter)

在处理程序中,您可以像这样修改计数器:

counter <- fmap visitorCounter getYesod
modifyIORef counter (+1)
count <- readIORef counter
-- ... display the count or something

注意AcidState的对称性.

对于站点计数器,我实际上建议使用TVars而不是IORef,因为多个客户端可能同时修改变量.然而,TVars的界面非常相似.

来自问题作者的跟进问题?

我将{visitorCounter :: TVar Int}放在我的基础类型中,并在处理程序中放置以下代码:

counter <- fmap visitorCounter getYesod
count <- readTVar counter

第一行编译正常,但第二行抛出此错误:

06004

我怎么能解决这个问题?

转载注明原文:哈斯克尔:Yesod和州 - 代码日志