I was reading through the code for a Toy URL Shortener. However, there's significant parts I just can't get my head around.
It has the following code:
data URLShort = URLShort { state :: AcidState URLStore }
For testing purposes, I wrote something like this in my own app:
data MyApp = MyApp { state :: Int }
I could then compile by changing
main = warpDebug 3000 MyApp
to
main = warpDebug 3000 (MyApp 42)
And I could then do reads of the state in handlers by doing
mystate <- fmap state getYesod
inspired by acid <- fmap state getYesod
in the article. However, I didn't know how to do writes.
I have also tried doing:
data MyApp = MyApp { state :: State Int Int }
But I didn't get far with this.
I was trying to work out how AcidState
works just by doing some simple similar examples, figuring that since AcidState
keeps everything in memory, I should be able to do the same?
Any sort of general explanation of what is going on here, and also perhaps how I'm missing the point would be very appreciated.
The
AcidState a
data type is not an immutable value all the way down; it contains references to mutable data internally. What is stored in Yesod-land in this case is simply an immutable reference to this data. When you update the state, you don't actually update the value in the foundation data type, but instead the memory that it points to.Every value in the Haskell world is immutable. However, a lot of things outside of the Realm of Haskell isn't immutable; for example, when you do
putStrLn
, the terminal mutates its display to show new content. TheputStrLn
action itself is an immutable pure value, but it describes how to perform an action involving mutation.There are other functions that also yield actions that perform mutations; if you do
ref <- newIORef 0
, you get a value describing an action that creates a mutable memory cell. If you then domodifyIORef ref (+1)
, you get a value describing an action that increments the value in that cell by1
. Theref
value is a pure value, it's simply a reference to a mutable cell. The code is also purely functional, because each piece only describes an action; nothing is mutable within the Haskell program.This is how
AcidState
implements its state: by using a system that manages state outside of the Haskell world. This is not "as bad as" having full mutability like in languages such as C, because in Haskell, you can control the mutability with the power of monads. UsingAcidState
is perfectly safe and does not involve the use ofunsafePerformIO
as far as I know.With
AcidState
in this case, you useopenAcidState emptyStore
in theIO
monad to create a new acid state (that line is a value describing an IO action that opens a new acid state). You usecreateCheckpointAndClose
to optionally save the acid state to disk safely. Finally, you use theupdate'
function to mutate the contents of an acid state.To create a "small state" yourself using
IORef
s (The simplest form of mutable state, except for maybe theST
monad), you first add a field like this to your foundation data type:You then do:
In a handler, you can modify the counter like this:
Note the symmetry to
AcidState
.For a site counter, I would actually recommend using
TVar
s instead ofIORef
s, because of the possibility that multiple clients might modify the variable simultaneously. The interface toTVar
s is very similar, however.Follow up question from question author?
I've placed
{ visitorCounter :: TVar Int }
in my foundation type, and the following code in the handler:The first line compiles fine, but the second throws this error:
How could I fix this?