Skip to content

Instantly share code, notes, and snippets.

@DarinM223
Last active June 22, 2018 06:43
Show Gist options
  • Save DarinM223/b00af0173122edb6fc7e496986427e4e to your computer and use it in GitHub Desktop.
Save DarinM223/b00af0173122edb6fc7e496986427e4e to your computer and use it in GitHub Desktop.
Rust and Haskell examples

Some examples in Rust and Haskell

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.

Getting/setting in nested state:

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'

Applying function on state:

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

Applying function on nested state:

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

Applying function on indexed nested state:

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

Applying functions on state with error handling:

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

Effectful traits/typeclasses:

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment