Skip to content

Instantly share code, notes, and snippets.

@DarinM223
Last active February 16, 2018 04:36
Show Gist options
  • Save DarinM223/c2f25957d31d7987b628b4093882a711 to your computer and use it in GitHub Desktop.
Save DarinM223/c2f25957d31d7987b628b4093882a711 to your computer and use it in GitHub Desktop.
Haskell stuff

How to use stack?

  1. Run stack new project_name to create a new stack project.
  2. 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 the package.yaml file. Editing the package.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
  3. To build with stack, run stack build.
  4. To run an executable with stack, run stack exec [project_name]-exe. To run with arguments add -- before your arguments.
  5. To run a repl run stack repl.
  6. To profile with stack, run stack build --profile. -- TODO: figure out extra steps

Flags to add to GHC

  • -W
  • -fshow-hole-constraints
  • -O2

Which state to use:

  1. If the state needs to be mutable (low level stuff) and the state is global, use an IORef.
  2. If the state needs to be mutable and the state is local, use an STRef.
  3. If the state needs to be mutable and read/writable from multiple threads, use an TVar.
  4. If the state can be pure, use State.

How to add error handling to my monads?

Use a monad transformer with ExceptT. -- TODO: More info on this + examples

How do I compose state?

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment