I've been learning Haskell recently and the hardest thing so far is figuring out how to map stuff that I already know how to do in other languages to Haskell, like updating nested structures, composing state, and handling errors with state.
For me I feel that the best way to do this is to show examples of equivalent code in Haskell and another language. Because I am familiar with Rust and Rust has types that are similar to Haskell's types while also being an imperative language, the comparison code is in Rust.
Most of this code requires basic knowledge of functional programming, typeclasses, monads, and monad transformers.
Many of the stateful "methods" in the Haskell code return a tuple of the return value and the updated state instead of being a StateT monad. The reasons for that are 1. the tuple versions are clearer and more concise than the monadic versions and 2. there is an extra step in converting the tuple function into a StateT which I want to cover instead of assuming every function is already StateT.
These packages are required:
- containers (for data structures like map and vector)
- mtl (for monad transformers)
- lens (for easy access/update of fields in nested state)
Import statements and language extensions have been removed from the top for conciseness so you have to add those in order for the code to compile.
struct Address {
name: String,
street: String,
city: String,
state: String,
zip_code: String,
}
struct Person { address: Address }
fn get_street(person: Person) -> String {
person.address.street
}
fn set_street(person: &mut Person, street: String) {
person.address.street = street;
}
data Address = Address
{ addressName :: String
, addressStreet :: String
, addressCity :: String
, addressState :: String
, addressZipCode :: String
}
makeFields ''Address
data Person = Person { personAddress :: Address }
makeFields ''Person
getStreet :: Person -> String
getStreet p = p ^. (address . street)
setStreet :: String -> Person -> Person
setStreet street' p = p & (address . street) .~ street'
getStreetM :: (Monad m) => StateT Person m String
getStreetM = do
street' <- use (address . street)
return street'
setStreetM :: (Monad m) => String -> StateT Person m ()
setStreetM street' = (address . street) .= street'
struct Deck { cards: Vec<Card> }
impl Deck {
fn take_card(&mut self) -> Option<Card> {
self.cards.pop()
}
fn discard_top(&mut self) {
self.cards.pop();
}
fn do_something(&mut self) {
let card = self.take_card();
self.discard_top();
if let Some(card) = card {
println!("Took card: {:?}", card);
}
}
}
data Deck = Deck { deckCards :: [Card] }
makeFields ''Deck
takeCard :: Deck -> (Maybe Card, Deck)
takeCard (Deck (c:cards)) = (Just c, Deck cards)
takeCard deck = (Nothing, deck)
discardTop :: Deck -> Deck
discardTop (Deck (_:cards)) = Deck cards
discardTop deck = deck
doSomething :: (MonadIO m) => StateT Deck m ()
doSomething = do
maybeCard <- state takeCard
modify discardTop
mapM_ (\c -> liftIO $ putStrLn $ "Took card: " ++ show c) maybeCard
struct Deck { cards: Vec<Card> }
impl Deck {
fn take_card(&mut self) -> Option<Card> {
self.cards.pop()
}
fn discard_top(&mut self) {
self.cards.pop();
}
}
struct Game { deck: Deck }
impl Game {
fn do_something(&mut self) {
let card = self.deck.take_card();
self.deck.discard_top();
if let Some(card) = card {
println!("Took card: {}", card);
}
}
}
data Deck = Deck { deckCards :: [Card] }
makeFields ''Deck
data Game = Game { gameDeck :: Deck }
makeFields ''Game
takeCard :: Deck -> (Maybe Card, Deck)
takeCard (Deck (c:cards)) = (Just c, Deck cards)
takeCard deck = (Nothing, deck)
discardTop :: Deck -> Deck
discardTop (Deck (_:cards)) = Deck cards
discardTop deck = deck
doSomething :: (MonadIO m) => StateT Game m ()
doSomething = do
maybeCard <- zoom deck $ state takeCard
zoom deck $ modify discardTop
mapM_ (\c -> liftIO $ putStrLn $ "Took card: " ++ show c) maybeCard
struct Player { cards: Vec<Card> }
impl Player {
fn take_card(&mut self) -> Option<Card> {
self.cards.pop()
}
fn discard_top(&mut self) {
self.cards.pop();
}
}
struct Game { players: HashMap<i32, Player> }
impl Game {
fn handle_player(&mut self, player_index: i32) {
let card = self.players.get_mut(&player_index).map(|ref mut p| {
let card = p.take_card();
p.discard_top();
card
});
if let Some(card) = card {
println!("Took card: {:?}", card);
}
}
}
import qualified Data.IntMap as IM
-- ...Other imports
data Player = Player { playerCards :: [Card] }
makeFields ''Player
data Game = Game { gamePlayers :: IM.IntMap Player }
makeFields ''Game
discardTop :: Player -> Player
discardTop (Player (_:cards)) = Player cards
discardTop p = p
takeCard :: Player -> (Maybe Card, Player)
takeCard (Player (c:cards)) = (Just c, Player cards)
takeCard p = (Nothing, p)
-- Helper function similar to `preuse` but with `zoom`.
prezoom l m = getFirst <$> zoom l (First . Just <$> m)
handlePlayer :: (MonadIO m) => Int -> StateT Game m ()
handlePlayer playerIndex = do
maybeCard <- prezoom (players . ix playerIndex) $ state takeCard
zoom (players . ix playerIndex) $ modify discardTop
mapM_ (\c -> liftIO $ putStrLn $ "Took card: " ++ show c) maybeCard
enum GameError {
NoDeckCard,
NoPlayerCard,
OtherError,
}
struct Deck { cards: Vec<Card> }
impl Deck {
fn take_card(&mut self) -> Result<Card, GameError> {
self.cards.pop().ok_or_else(|| GameError::NoDeckCard)
}
fn discard_top(&mut self) -> Result<(), GameError> {
self.take_card().map(|_| ())
}
}
fn either_val<T>(opt: Option<T>) -> Result<T, GameError> {
opt.ok_or_else(|| GameError::OtherError)
}
struct Game { deck: Deck }
impl Game {
fn do_something(&mut self) -> Result<(), GameError> {
let v = either_val(Some(2))?;
let card = self.deck.take_card()?;
self.deck.discard_top()?;
Ok(())
}
}
data GameError = NoDeckCard
| NoPlayerCard
| OtherError
data Deck = Deck { deckCards :: [Card] }
makeFields ''Deck
data Game = Game { gameDeck :: Deck }
makeFields ''Game
takeCard :: Deck -> Either GameError (Card, Deck)
takeCard (Deck (c:cards)) = Right (c, Deck cards)
takeCard _ = Left NoDeckCard
discardTop :: Deck -> Either GameError Deck
discardTop (Deck (_:cards)) = Right $ Deck cards
discardTop _ = Left NoDeckCard
eitherVal :: Maybe a -> Either GameError a
eitherVal (Just v) = Right v
eitherVal _ = Left OtherError
-- Helper function similar to `state` but lifts either errors into ExceptT.
eitherState :: (MonadState s m, MonadError e m) => (s -> Either e (a, s)) -> m a
eitherState f = either throwError (state . const) . f =<< get
-- Helper function similar to `modify` but lifts either errors into ExceptT.
eitherModify :: (MonadState s m, MonadError e m) => (s -> Either e s) -> m ()
eitherModify f = either throwError (modify . const) . f =<< get
doSomething :: (MonadIO m) => StateT Game (ExceptT GameError m) ()
doSomething = do
v <- either throwError return $ eitherVal (Just 2)
card <- zoom deck $ eitherState takeCard
zoom deck $ eitherModify discardTop
liftIO $ putStrLn $ "Took card: " ++ show card
pub trait Store<K, V> {
fn getValue(&self, key: &K) -> io::Result<Option<V>>;
fn setValue(&mut self, key: &K, value: V) -> io::Result<()>;
fn deleteValue(&mut self, key: &K) -> io::Result<()>;
}
pub fn swap<S, K, V>(store: &mut S, key1: &K, key2: &K) -> io::Result<()>
where
S: Store<K, V>,
{
let v1 = store.getValue(key1)?;
let v2 = store.getValue(key2)?;
if let (Some(v1), Some(v2)) = (v1, v2) {
store.setValue(key2, v1)?;
store.setValue(key1, v2)?;
}
Ok(())
}
class (Monad m) => StoreM k v m | m -> k v where
getValue :: k -> m (Maybe v)
setValue :: k -> v -> m ()
deleteValue :: k -> m ()
swap :: (StoreM k v m) => k -> k -> m ()
swap k1 k2 = do
maybeV1 <- getValue k1
maybeV2 <- getValue k2
case (maybeV1, maybeV2) of
(Just v1, Just v2) -> do
setValue k2 v1
setValue k1 v2
_ -> return ()
Note that the Haskell version of swap is much more general and can work with pure code, IO based code, code with extra error handling, and async code without having to change anything.