- Run
stack new project_name
to create a new stack project. - Edit
package.yaml
instead of the cabal file. This is confusing because many older answers recommend editing the cabal file, but recent versions of stack use HPack which autogenerates the cabal file from thepackage.yaml
file. Editing thepackage.yaml
file is better than manually editing the cabal file because it automatically discovers your modules and adds them to the cabal (otherwise you have to add every file to the modules section). Examples on HPack syntax are in https://github.com/sol/hpack - To build with stack, run
stack build
. - To run an executable with stack, run
stack exec [project_name]-exe
. To run with arguments add--
before your arguments. - To run a repl run
stack repl
. - To profile with stack, run
stack build --profile
. -- TODO: figure out extra steps
- -W
- -fshow-hole-constraints
- -O2
- If the state needs to be mutable (low level stuff) and the state is global, use an IORef.
- If the state needs to be mutable and the state is local, use an STRef.
- If the state needs to be mutable and read/writable from multiple threads, use an TVar.
- If the state can be pure, use State.
Use a monad transformer with ExceptT. -- TODO: More info on this + examples
Oftentimes its necessary to have some state contain nested substates. For example, the main state for a card game will have to contain the deck state. Here's an example of this in Rust:
pub struct Deck(cards: Vec<Card>)
impl Deck {
pub fn draw(&mut self) -> Card {
// ...
}
}
pub struct Game {
deck: Deck,
}
impl Game {
pub fn run_turn(&mut self) {
self.deck.draw(); // Calls a function on a nested state.
}
}
When translating this code to Haskell I ran into a problem when trying to call
"methods" on nested states. For example, draw
would have type StateT [Card] m Card
and
runTurn
would have type StateT Game m ()
. Since [Card]
and Game
are incompatible types I would
have to unwrap the deck monad with runStateT
and retrieve the tuple (returnVal, deck')
and
then update the deck in the game record with deck'
. This is a lot of boilerplate and gets even worse
with multiple levels of nesting. Is there a better way to do this?
It's possible that draw could take in the state of Game
instead of [Card]
. This will couple draw
into that specific
game and make the deck less modular.
I found that the lens library has a zoom
function that lets you zoom into a substate and apply a monadic state operation
inside. This allows me to do something similar to the Rust code:
type Deck = [Card]
data Game = Game
{ gameDeck :: Deck
}
makeFields ''Game
draw :: (Monad m) => StateT [Card] m Card
draw = ...
runTurn :: (MonadIO m) => StateT Game m ()
runTurn = do
card <- zoom deck draw -- Zoom the draw function into the deck substate
A problem with using lens and zoom is when you have a custom monad type. The only way I found to deal with this is to have my monad type take in the state as a parameter and then implement Zoom for that type. Then for every function that changes a nested state I use MonadState instead of returning the exact type to avoid having to convert StateT into my monad type.
-- GameT has to pass in the state s in order to be zoomable
newtype GameT s m a = GameT
{ unGameT :: StateT s m a
} deriving (Functor, Applicative, Monad, MonadIO, MonadState s)
type instance Zoomed (GameT s m) = Focusing m
instance (Monad m) => Zoom (GameT s m) (GameT t m) s t where
zoom l m = GameT $ zoom l $ unGameT m
-- Make draw generic over any type with state [Card]
-- Prevents having to convert StateT into GameT
draw :: (MonadState [Card] m) => m Card
draw = ...
runTurn :: (MonadIO m) => GameT Game m ()
runTurn = do
card <- zoom deck draw