There are quite a few ways to store mutable data in Haskell. Let’s talk about some of them! Specifically, we will focus on mutable containers that store a single value that can be modified by one or more threads at any given time.
I’m not going to go into a ton of detail here - I just want to give an overview. I have provided links to the documentation and other resources at the end of each section for further reading.
First up is IORef
, the simplest of all containers. It is a sectioned off bit of mutable memory for any number of threads to read/modify willy-nilly.
We can read this diagram as follows:
IO
monad/context.IORef
was created in IO
somewhere and provided to two threads: t1
and t2
.t1
writes a value to the IORef
using writeIORef :: IORef a -> a -> IO ()
t2
writes a value to the same IORef
.t1
reads the IORef
using readIORef :: IORef a -> IO a
The following diagrams will follow the same general struture: time increases as we move downwards along a thread, and certain actions are taken within those threads.
IORef
s are not very safe. They are highly succeptible to race conditions and other unintended behavior, and should be used with caution. For example, in our diagram: t2
modifies the IORef
after t1
wrote to it - t1
probably expected that readIORef
would return whatever it placed there. That is not the case, because t2
modified it between the write and read steps of t1
.
MVar
s represent a location in memory that holds a value as well. However, MVar
s come with the guarantee that no two threads are modifying a variable at the same time.
An MVar
is either empty or full of an a
. When we try to takeMVar
on an empty MVar
, the current thread blocks (indicated by a black line) until a value is put back into the MVar
. GHC
’s runtime is pretty good at determining when a thread is blocked indefinitely on an MVar
read, so we don’t often have to worry about a thread hanging due to a bad program (for too long).
MVar
s are still succeptible to race conditions, but are great for simple concurrent tasks like synchronization and basic communication between threads.
TVar
s solve a different problem. They are associated with a mechanism called Software Transactional Memory - STM
- - a construct that allows us to compose primitive operations and run them sequentially as a transaction. Think database transaction: if one STM
action in a chain fails, all previous actions taken in that chain are rolled back accordingly.
TVar
s have a similar API to MVar
, with one major difference: They can’t ever be empty. , which is commonly executed as an atomic transaction using the function TVar
s can only be used in a singular threadatomically :: STM a -> IO ()
.
STM
provides a bunch of very useful primitives for working with transactions, and is worth exploring:
EDIT: TVar
s can be used in multiple threads. If a TVar
is modified by a different thread during the execution of a transaction, the transaction is retried. /u/cgibbard
explains these semantics quite nicely in a comment on reddit.
This diagram should look pretty familiar! TMVar
s are a mash between TVar
s and MVar
s, as you might expect from its name. They can be composed transactionally just like TVar
s, but can also be empty, and shared across many threads.
Since all of these TMVar
actions live in STM
, they can be run in the same manner as when we use regular TVar
s.
STRef
s are a completely different type of mutable container. They are restricted to a single thread, but guarantee that they never escape (they are thread-local). They live in a context called ST
, indicating a stateful thread.
The s
value in the type of ST
and STRef
is a reference to the thread that the ST
computation is allowed to access.
ST
and STRef
s are mainly used to gain performance when you need to be closer to memory, but don’t want to give up safety.
Til next time!
Ben
]]>JSON is ubiquitous nowadays, perhaps most importantly for web APIs. We’ll probably need to interact with (or build) one of those at some point, so we must be able to handle JSON in Haskell, right?
Yep - also it’s pretty easy. Let’s talk about it! First, some boilerplate to get out of the way:
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
import Control.Lens ((^.), (^?))
import Control.Lens.TH
import Data.Aeson.Lens
import Data.Aeson
import Data.Aeson.Types
import Data.Aeson.TH
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.Text as T
import GHC.Generics
Try not to get too overwhelmed with that giant chunk of imports and extensions. Most of it is only for the template haskell we’ll be using later. The important bit for now is Data.Aeson
from the aeson
package, which allows us to seamlessly work with JSON. We’ll need lens
and lens-aeson
packages later on.
Let’s pretend we have some API we’re building and want to generate a static blob of JSON for the front page. How about this:
index :: Value
index = object
[ "message" .= String "Congrats!"
, "status" .= String "YOU_GOT_HERE_SO_OBVIOUSLY_SUCCESSFUL"
, "metadata" .= object [
"version" .= Number 9
]
]
Value
is the type of JSON values in Haskell. We can build up an object using the object
functions, and a mapping from keys to Value
s, generated by String
, Number
, and other functions.
We can encode a Value
with encode
. It produces a lazy ByteString
:
BL.putStrLn $ encode index
-- { "status":"YOU_GOT_HERE_SO_OBVIOUSLY_SUCCESSFUL","metadata":{"version":9},"message":"Congrats!"}
Next up, I’d like to show how a client might interact with this thing. If they have the unpacked Value
, they can access the value at the message
key like this:
index ^. key "message" . _String
-- "Congrats!"
What’s even better is that we don’t even need to unpack the Value
! We can operate directly on the encoded JSON:
encode index ^. key "message" . _String
-- "Congrats!"
We can dig into the nested fields safely using ^?
:
index ^? key "metadata" . key "app_version" . _Number
-- Just 9.0
All Lens
idioms apply. It’s easy to get, set, or modify arbitrary fields of JSON objects this way.
Let’s say we want to start building up our application and require more type safety. aeson
makes it easy to generate encoding and decoding mechanisms for your data types. For instance, we can define the following two types, generate lenses (via makeFields
) and ToJSON
and FromJSON
instances using a bit of template haskell:
data Metadata = Metadata
{ _metadataAppVersion :: Int
} deriving (Show, Eq, Generic)
$(deriveJSON
defaultOptions
{ fieldLabelModifier = camelTo2 '_' . drop (T.length "_metadata")
} ''Metadata)
$(makeFields ''Metadata)
data IndexResponse = IndexResponse
{ _indexResponseMessage :: T.Text
, _indexResponseStatus :: T.Text
, _indexResponseMetadata :: Metadata
} deriving (Show, Eq, Generic)
$(deriveJSON
defaultOptions
{ fieldLabelModifier = camelTo2 '_' . drop (T.length "_indexResponse")
} ''IndexResponse)
$(makeFields ''IndexResponse)
Here’s the same index
structure, now typed more explicitly:
indexResponse :: IndexResponse
indexResponse = IndexResponse
"Congrats!"
"YOU_GOT_HERE_SO_OBVIOUSLY_SUCCESSFUL"
(Metadata 9)
BL.putStrLn $ encode indexResponse
-- {"message":"Congrats!","status":"YOU_GOT_HERE_SO_OBVIOUSLY_SUCCESSFUL","metadata":{"app_version":9}}
Note that the appVersion
field gets automatically converted from camelCase
to snake_case
with the camelTo2
option from Data.Aeson.Types
. Handy!
We can check that encoding and decoding works, and use more type-safe lenses (in this case, message
, which was generated by makeFields
):
(decode (encode indexResponse) :: Maybe IndexResponse) ^. _Just . message
-- "Congrats!"
As you can see, dealing with JSON in Haskell is a breeze! What other tips and tricks do you use when dealing with JSON (de)serialization (in Haskell or otherwise)?
Until next time,
Ben
You can read more about aeson
and lens-aeson
in the docs:
It’s likely that you’ll have to deal with environment variables at some point. What I’ll describe here is a kicking-off point for robust environment handling with little overhead. We’ll build a tiny library you can drop into any application that will make dealing with environment variables for configuration a lot easier. Then I’ll show some example usage.
This is all built on top of System.Environment
, which isn’t super nice to use in its raw form. In particular, there no implicit facilities for type coercion, fallback values, or composability. We’ll address those problems here.
You’ll need the following libraries to get run the code in this post: transformers
, split
and safe
.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import System.Environment hiding (getEnv)
import Control.Monad.Trans.Maybe
import Control.Monad.IO.Class
import Control.Applicative
import Control.Monad
import Safe
import Data.List.Split
newtype Env a = Env{ unEnv :: MaybeT IO a }
deriving
( Functor
, Applicative
, Monad
, MonadIO
, Alternative
, MonadPlus
)
runEnv :: Env a -> IO (Maybe a)
runEnv = runMaybeT . unEnv
-- Lift a `Maybe` into the `Env` context.
liftMaybe :: Maybe a -> Env a
liftMaybe = Env . MaybeT . pure
-- Get an environment variable in its
-- raw form.
getEnv :: String -> Env String
getEnv key =
liftIO (lookupEnv key) >>= liftMaybe
-- Pull an environment variable from
-- the environment, using a parsing
-- function for conversion.
env :: (String -> Maybe a) -> String -> Env a
env f key = liftMaybe . f =<< getEnv key
-- Pull an optional value from the
-- environment.
optional
:: (String -> Maybe a)
-> String
-> Env (Maybe a)
optional f key =
(f <$> getEnv key) <|> pure Nothing
-- Exploit the `Read` interface for a type
-- to read an environment variable.
readEnv :: Read a => String -> Env a
readEnv = env readMay
This code was adapted from a comment on reddit (credit to u/Tekmo).
I think this mini-library is “good enough” for a lot of applications. One major drawback is that it doesn’t report missing or improperly formatted environment variables - functionality can be added in a relatively straightforward way, however, with a MonadThrow
constraint. This is the simplest thing that does the job well, though, so we’ll run with it.
For my example application, I want to be able to pull configuration information from a set of environment variables.
We can use our mini-library to do this:
data Stage
= Testing
| Development
| Staging
| Production
deriving (Show, Read)
data Version = Version Int Int Int
deriving Show
data MyEnvironment = MyEnvironment
{ stage :: Stage
, identifier :: Maybe String
, version :: Version
} deriving (Show)
-- Parse a semantic version string like v1.3.3
parseVersion :: String -> Maybe Version
parseVersion versionString =
case splitOn "." semver of
[major, minor, patch] ->
Version
<$> readMay major
<*> readMay minor
<*> readMay patch
_ -> Nothing
where semver = tail versionString
-- An environment reader for `MyEnvironment`
myEnv :: Env MyEnvironment
myEnv = MyEnvironment
<$> (readEnv "APP_STAGE" <|> pure Production)
<*> optional Just "APP_ID"
<*> env parseVersion "APP_VERSION"
main :: IO ()
main = runEnv myEnv >>= print
Running this as an executable my_app
, we get the following output (formatting mine):
$ APP_STAGE=Testing APP_VERSION=v1.1.1 APP_ID=its_me_mario my_app
Just (
MyEnvironment
{ stage = Testing
, identifier = Just "its_me_mario"
, version = Version 1 1 1
}
)
Or, with some missing/incomplete information:
$ APP_STAGE=nonexistent APP_VERSION=v1.1.4 my_app
Just (
MyEnvironment
{ stage = Production
, identifier = Nothing
, version = Version 1 1 4
}
)
What do you use for handling environment variables in Haskell? Do you use environment variables for different purposes that you’d like to see covered? What else would you like to see covered in future Haskell Bits? Let me know in the comments!
Ben
]]>Today I want to talk about getting Haskell programs to talk to databases.
We’ll set out to build something pretty simple: A single table, filled with records of board games. I want to keep the focus of the post on connecting to various database types, so our board game record will be extremely simple. Here’s an example of the table we’ll be working with:
id | name | designer | year |
---|---|---|---|
1 | Lords of Waterdeep | Peter Lee | 2012 |
3 | Agricola | Uwe Rosenberg | 2007 |
4 | Race for the Galaxy | Thomas Lehmann | 2007 |
Important note: I originally had “Chess” in here as a board game with no known year, but realized after writing the post and all of the associated code that I don’t know who designed it, either! The designer
should technically be nullable as well, but it is not in the schema I’ve defined. Keep that in mind as you read through the post!
I’m going to walk through inserting a single element into a table of the aforementioned schema, but it will support all of the records above. Try inserting them as an exercise!
We’re going to talk about connecting to three different databases:
…with two different “flavors” of libary. First, the lower-level “-simple” strain of database libraries, and later, the higher-level persistent
library.
sqlite
with sqlite-simple
We need a database:
$ sqlite3 board_games.db "CREATE TABLE IF NOT EXISTS board_games (id INTEGER PRIMARY KEY,
name TEXT NOT NULL, designer TEXT NOT NULL, year INTEGER);"
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad.State
import Control.Monad.Reader
import Data.Text
import Database.SQLite.Simple
import Data.Maybe (fromJust)
data BoardGame = BoardGame
{ name :: Text
, designer :: Text
, year :: Maybe Int
} deriving (Show, Eq)
instance FromRow BoardGame where
fromRow = BoardGame <$> field <*> field <*> field
instance ToRow BoardGame where
toRow BoardGame{..} = toRow (name, designer, year)
createGame :: BoardGame -> ReaderT Connection IO (Int, BoardGame)
createGame game = ask >>= \conn -> do
liftIO $ execute
conn
"INSERT INTO board_games (name, designer, year) VALUES (?,?,?)"
game
boardGameId <- fromIntegral <$> liftIO (lastInsertRowId conn)
game' <- fromJust <$> readGame boardGameId
pure (boardGameId, game')
readGame :: Int -> ReaderT Connection IO (Maybe BoardGame)
readGame boardGameId = ask >>= \conn -> do
games <- liftIO $ query
conn
"SELECT name, designer, year FROM board_games WHERE id = ?"
(Only boardGameId)
pure $ case games of
[g] -> Just g
_ -> Nothing
main :: IO ()
main = do
conn <- open "board_games.db"
flip runReaderT conn $ do
result <- createGame $ BoardGame
"Cosmic Encounter"
"Bill Eberle"
(Just 2008)
liftIO $ print result
(packages needed: sqlite-simple
, mtl
, text
)
This is the core logic we’ll be implementing with each library. We’ll just be modifying this piecemeal for the rest of the implementations. It’s the longest code sample in the post, so don’t run off!
Let’s walk through this a little, just once. Here’s what we’re doing:
conn
)ReaderT
computationsIn order to do this, we have to write a little bit of boilerplate. First is the model definition for BoardGame
, and instances of ToRow
and FromRow
, which allow us to serialize and deserialize from the sqlite
representation of a BoardGame
. We also have to write the actual SQL commands; not a whole lot is abstracted away from us.
(I lied a little - the model definition is not strictly necessary but it’s typically good to pull data into your program’s domain, so I suggest doing this step.)
postgresql
with postgresql-simple
We’ll need a database, again (note the syntax is slightly different):
CREATE TABLE IF NOT EXISTS board_games (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
designer TEXT NOT NULL,
year INTEGER
);
Otherwise, we don’t have a ton to change. We only have to touch a few things:
Connection
is slightly different, because we’re no longer using a flat filelastInsertRowId
is not a primitive. Postgres supports RETURNING
syntax so we can get the id
when we insert.That’s actually…it. Here are the imports we need:
import Database.PostgreSQL.Simple
import Database.PostgreSQL.Simple.FromRow
import Database.PostgreSQL.Simple.ToRow
Here is the new createGame
:
createGame :: BoardGame -> ReaderT Connection IO (Int, BoardGame)
createGame game = ask >>= \conn -> do
[Only boardGameId] <- liftIO $ query
conn
"INSERT INTO board_games (name, designer, year) VALUES (?,?,?) RETURNING id"
game
game' <- fromJust <$> readGame boardGameId
pure (boardGameId, game')
And here is the new connection procuring mechanism (this uses a postgres connection string, so suit it to your needs):
-- Inside `main`:
conn <- connectPostgreSQL "host=localhost port=5432 connect_timeout=10"
(packages needed: postgresql-simple
, mtl
, text
)
Check out the full source here.
mysql
with mysql-simple
The last database we’ll get this running on is mysql
. mysql-simple
was the original “-simple” library for database management. However, it’s also the most different.
First, creating the table:
CREATE TABLE `board_games` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL DEFAULT '',
`designer` varchar(64) NOT NULL DEFAULT '',
`year` int(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
We’ll need the language extension BangPatterns
(not necessary, but recommended):
{-# LANGUAGE BangPatterns #-}
and some updated imports:
import Database.MySQL.Simple
import Database.MySQL.Simple.QueryParams
import Database.MySQL.Simple.QueryResults
import Database.MySQL.Simple.Param
import Database.MySQL.Simple.Result
Our BoardGame
type can stay the same, but ToRow
and FromRow
get replaced with the typeclasses QueryParams
and QueryResults
respectively. Their interfaces are a little different too:
instance QueryParams BoardGame where
renderParams BoardGame{..} = [render name, render designer, render year]
instance QueryResults BoardGame where
convertResults [fa,fb,fc] [va,vb,vc] = BoardGame a b c
where !a = convert fa va
!b = convert fb vb
!c = convert fc vc
convertResults fs vs = convertError fs vs 3
We need to go back to selecting the last insert id, but there’s no primitive for that, so we inline it and make some small modifications to createGame
:
createGame :: BoardGame -> ReaderT Connection IO (Int, BoardGame)
createGame game = ask >>= \conn -> do
liftIO $ execute
conn
"INSERT INTO board_games (name, designer, year) VALUES (?,?,?)"
game
[Only boardGameId] <- liftIO $ query_ conn "SELECT LAST_INSERT_ID()"
game' <- fromJust <$> readGame boardGameId
pure (boardGameId, game')
Connection info is provided using ConnectInfo
instead of a postgres connection string:
connectInfo :: ConnectInfo
connectInfo = defaultConnectInfo
{ connectDatabase = "board_games"
}
The last step is to swap the connection line in main
to:
-- Inside main
conn <- connect connectInfo
(packages needed: postgresql-simple
, mtl
, text
)
Check out the full source here
These libraries are not all that different - they’re all inspired by one-another. You may find documentation or tutorials that use one of these libraries and need to use another; I hope this helps translate between the languages of the three.
Next, I’d like to talk about persistent
. persistent
is a higher-level, more fully featured set of database tooling. It’s a lot more “magical” than the “-simple” libraries, but removes the necessity of some of the boilerplate and inlining of raw SQL that comes with the “-simple” variants. It’s also backend-agnostic which makes for a uniform interface.
sqlite
with persistent
We’ll need a data type definition in persistent
’s template haskell DSL, which we’ll put in a module called Types
:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Types where
import Database.Persist.TH
share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
BoardGame sql=board_games
name String
designer String
year Int Maybe
UniqueName name
deriving Show
|]
The translated source:
{-# LANGUAGE OverloadedStrings #-}
import Types
import Database.Persist.Sqlite
import Control.Monad.Logger
import Control.Monad.IO.Class
printIO :: (MonadIO m, Show a) => a -> m ()
printIO = liftIO . print
createGame :: MonadIO m => BoardGame -> SqlPersistT m (Entity BoardGame)
createGame = insertEntity
readGame :: MonadIO m => Int -> SqlPersistT m (Maybe BoardGame)
readGame = get . toSqlKey . fromIntegral
main :: IO ()
main =
runStdoutLoggingT
. withSqlitePool "board_games.db" 3
. runSqlPool
$ do
result <- createGame $ BoardGame
"Cosmic Encounter"
"Bill Eberle"
(Just 2008)
liftIO $ print result
-- This is the easiest way to re-read a record:
get (entityKey result) >>= printIO
-- Getting by id
readGame 1 >>= printIO
-- Get by name too:
getBy (UniqueName "Cosmic Encounter") >>= printIO
(packages needed: persistent
, persistent-template
, persistent-sqlite
, mtl
, monad-logger
)
A couple of things:
SqlPersistT
can be thought of as a “SQL Statement Context” - you can write arbitrary queries in these blocks. Each of these is run in its own transaction by default, so be careful! Any exceptions will roll back any changes. (thanks to /u/ephrion for bringing up this point!)BoardGame
and UniqueName
are types generated by the template haskell in the Types
module.withSqliteConn
instead of withSqlitePool
.Entity
is a type consisting of a Key
and a model (in our case, BoardGame
) - this is analogous to (Int, BoardGame)
with the -simple
libraries.runStdoutLoggingT
prints debug SQL statements to stdout. It can be replaced with runNoLoggingT
or runStderrLoggingT
to modify this behavior.This is just the tip of the iceberg. See the Yesod book’s chapter on persistent, the persistent documentation and specifically the Database.Persist.Class module for more information.
Also see the persistent-sqlite documentation.
postgres
with persistent
Change the Database.Persist.Sqlite
import to Database.Persist.Postgres
.
The only other thing to change in order to connect to postgres
instead is the second line of main
, to:
. withPostgresqlPool "host=localhost port=5432 connect_timeout=10" 3
(packages needed: persistent
, persistent-template
, persistent-postgresql
, mtl
, monad-logger
)
Everything else works the same!
And the persistent-postgresql documentation
mysql
with persistent
Connecting to mysql is almost as simple. Change the import to Database.Persist.MySQL
, add the ConnectInfo
definition:
connectInfo :: ConnectInfo
connectInfo = defaultConnectInfo
{ connectDatabase = "board_games"
}
and change the same line in main
to:
. withMySQLPool connectInfo 3
(packages needed: persistent
, persistent-template
, persistent-mysql
, mtl
, monad-logger
)
Again, everything else works the same.
And the persistent-mysql documentation
I’ve never attempted to run a database-backed haskell application on Windows, so I must apologize for not showing off how to connect to SQL Server. AFAIK, the only current package that supports this is HDBC, which I’ve not used.
What is your preferred way of interacting with databases in Haskell? Which database do you think is the most pleasant to work with? What parts of this post would you like to see expanded on in the future? Let me know in the comments!
Ben
]]>Are you comfortable creating data types, manipulating lists, composing functions, etc, but not sure how to make a “useful program” with haskell? This is a very common stumbling block when learning. It might be the complaint I’ve heard the most.
In this Haskell Bit, I want to walk through a pattern I have commonly seen in haskell applications. The pattern isn’t specific to haskell - it’s commonplace in tons of programming environments. It’s just a little less obvious how to get here with Haskell.
Here’s the pattern:
The mtl
package will need to be installed to run these examples.
In haskell, we have to be explicit about our the shape of our state and environment. A common way to represent a program with access to these basic needs, and not much else, is with the following data type:
import Control.Monad.Reader
import Control.Monad.State
type Program state config a = StateT state (ReaderT config IO) a
This data type expresses the following:
state
)config
in our programFor those who aren’t familiar, this is called a “monad transformer stack”. It’s just an expression of the effects our program can have.
With this, we’re going to build something extremely contrived. It should demonstrate the utility of this pattern, however. Here’s what we’ll do:
COUNT_BY
, and COUNT_UP_TO
0
, and count by COUNT_BY
steps, up to COUNT_UP_TO
, printing out each value.To do this, we need IO (to print), a read-only environment (to store the environment variables in), and some state (the current count). Sounds like it fits the pattern. Let’s see what it looks like!
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.IO.Class
import Data.Monoid
import System.Environment
type Program state config a = StateT state (ReaderT config IO) a
-- Run a `Program` with a given state and config, returning
-- a final value and the final state of the `Program`
runProgram :: Program s c a -> s -> c -> IO (a, s)
runProgram p s c = runReaderT (runStateT p s) c
data CounterState = CounterState { currentCount :: Integer }
data CounterConfig = CounterConfig
{ countBy :: Integer
, countUpTo :: Integer
}
-- A more specific type for our Counter program
type Counter a = Program CounterState CounterConfig a
-- The initial state we're starting with
initialState :: CounterState
initialState = CounterState 0
-- Some code to read from our environment variables.
-- Note: This is unsafe, and if either environment variable is
-- a) not set, or
-- b) not formatted like an integer,
-- the program will currently error out.
getConfig :: IO CounterConfig
getConfig = do
countBy' <- read <$> getEnv "COUNT_BY"
countUpTo' <- read <$> getEnv "COUNT_UP_TO"
pure $ CounterConfig countBy' countUpTo'
-- Our actual program ("business logic")
counter :: Counter ()
counter = do
count <- gets currentCount
countUpTo' <- lift $ asks countUpTo
unless (count > countUpTo') $ do
liftIO . putStrLn $ "Current count: " <> show count
countBy' <- lift $ asks countBy
let newCount = count + countBy'
modify (\st -> st{ currentCount = newCount })
counter
main :: IO ()
main = do
config <- getConfig
void $ runProgram counter initialState config
To run this, assuming it’s compiled to a program called counter
:
$ COUNT_BY=13 COUNT_UP_TO=100 counter
Current count: 0
Current count: 13
Current count: 26
Current count: 39
Current count: 52
Current count: 65
Current count: 78
Current count: 91
Not so bad! This is totally enough to get started with some more complex programs. Anything below this point is just polish. I will go into all of these polishing modifications in more detail in later posts.
Anyway, the following things are a little ugly right now, in my opinion:
lift
in counter
modify
call in counter
is not a cute line of codeIO
as a base monadThe first thing I want to do is abolish the lift
calls in counter
.
We’ll need to add the ConstraintKinds
and FlexibleContexts
extensions to get this to compile, but here’s an updated program:
-- Add this to the top of the file:
{-# LANGUAGE ConstraintKinds#-}
{-# LANGUAGE FlexibleContexts#-}
--
-- An interface that describes the effects our program can have.
type MonadCounter m =
( MonadState CounterState m
, MonadReader CounterConfig m
, MonadIO m
)
counter :: MonadCounter m => m ()
counter = do
count <- gets currentCount
countUpTo' <- asks countUpTo
unless (count > countUpTo') $ do
liftIO . putStrLn $ "Current count: " <> show count
countBy' <- asks countBy
let newCount = count + countBy'
modify (\st -> st{ currentCount = newCount })
counter
Everything else can stay the same. We’ve begun programming against the MonadCounter
interface, which Counter
just happens to satisfy, so we can still use our runProgram
function. The interface contains the State
/Reader
functions, but removes the need for lift
, which is nice.
Note: If we remove the
MonadIO
constraint inMonadCounter
, we’re no longer bound to usingIO
as our program’s base monad. It’s necessary for our current program (sinceliftIO
is called), but for others it may not be.
Next, let’s handle the ugly call to modify
. We can clean this up with lenses. I’ll cover these in greater detail in a later post. For now, we’ll need a separate module, Types
, containing the following (we also need the lens
library):
{-# LANGUAGE TemplateHaskell #-}
module Types where
import Control.Lens
data CounterState = CounterState
{ _currentCount :: Integer
}
data CounterConfig = CounterConfig
{ _countBy :: Integer
, _countUpTo :: Integer
}
$(makeLenses ''CounterState)
$(makeLenses ''CounterConfig)
This will give us lenses for currentCount
, countBy
, and countUpTo
. We can then write counter
as:
counter :: MonadCounter m => m ()
counter = do
count <- use currentCount
countUpTo' <- view countUpTo
unless (count > countUpTo') $ do
liftIO . putStrLn $ "Current count: " <> show count
countBy' <- view countBy
currentCount += countBy'
counter
I think that’s quite nice and readable.
I’m going to postpone talking about reading environment variables because I want to dedicate a whole Haskell Bit to reading configuration safely, and this one is getting somewhat long.
Is there a topic you would like to see covered in the future? Do you start your projects in a wildly different way? Let me know in the comments!
Ben
]]>Haskell Bits is a new series of bite-sized posts that I hope will empower people to “get it done and move on”, providing useful information and links to learn more if desired. I’ll be providing full main
files in each example (with imports!) to make porting this stuff into your own project as frictionless as possible. This first “Haskell Bit” will cover randomness.
You need at least two things to produce a random number:
That’s all for a single number.
Most programming languages will hide these details from you unless you need them. Most of the time, you can just call random()
and get a random number (typically between 0 and 1), using a seed value generated from some system variable that is always changing (current time in very small units is common).
The simplest way to replicate this behavior in Haskell is by using the System.Random
module, part of the random
package.
We can use randomIO
and randomRIO
to pull from a global RNG:
import System.Random
main :: IO ()
main = do
-- A random `Double` between 0 and 1
(randomIO :: IO Double) >>= print
-- A random `Int` between 1 and 6 (A die roll)
randomRIO (1, 6) >>= print
This is pretty much the interface that most other languages start with. Better would be to separate out IO as much as possible from the inevitable rest of our program. We can do that by confining IO usage to one operation: coming up with an initial RNG.
import System.Random
dieRoll :: RandomGen g => g -> (Int, g)
dieRoll = randomR (1, 6)
main :: IO ()
main = do
-- New generator, generated from the system RNG
gen <- newStdGen
let (result, newGen) = dieRoll gen
print result
let (newResult, newNewGen) = dieRoll newGen
print newResult
We don’t want to duplicate this code every time we want to add a new die roll. The next logical step would be to sprinkle in some State
to store the current RNG in:
import System.Random
import Control.Monad.State
dieRoll :: RandomGen g => State g Int
dieRoll = state (randomR (1, 6))
twoDice :: RandomGen g => State g Int
twoDice = (+) <$> dieRoll <*> dieRoll
main :: IO ()
main = do
gen <- newStdGen
print (evalState twoDice gen)
Now we can run more complex programs that employ random numbers. Note that newStdGen
can be replaced with mkStdGen :: Int -> StdGen
if you want to provide an integral seed instead of using the global StdGen
.
You can avoid some of the state boilerplate and get a few more benefits by bringing in the MonadRandom
package. Here’s some code that accomplishes the same goal using MonadRandom
:
import System.Random
import Control.Monad.Random
dieRoll :: RandomGen g => Rand g Int
dieRoll = getRandomR (1, 6)
twoDice :: RandomGen g => Rand g Int
twoDice = (+) <$> dieRoll <*> dieRoll
main :: IO ()
main = do
gen <- newStdGen
print (evalRand twoDice gen)
Apart from providing a nice way to write (slightly) terser randomness code, MonadRandom
is more explicit about the domain we’re working in, and ships with a couple of killer utilities; namely, the minimalistic sampling functions uniform
and fromList
(also weighted
from MonadRandom 0.5
). This program, for example, generates a list of 20 moves that might come up in a Dance Dance Revolution song:
import Control.Monad
import Control.Monad.Random
import System.Random
data Direction = U | D | L | R deriving (Show, Eq)
step :: RandomGen g => Rand g Direction
step = uniform [U,D,L,R]
stepWeighted :: RandomGen g => Rand g Direction
stepWeighted =
fromList [(U, 1), (D, 1), (L, 50), (R, 100)]
danceDanceRevolutionScroll :: RandomGen g => Rand g [Direction]
danceDanceRevolutionScroll = replicateM 20 $ do
weightIt <- uniform [True, False]
if weightIt then stepWeighted else step
main :: IO ()
main = do
gen <- newStdGen
print (evalRand danceDanceRevolutionScroll gen)
fromList
lets you specify weights for your random elements. L
and R
will probably show up a lot more than the other two directions when this is run.
MonadRandom
supplies some other conveniences as well, but it’s not crazy stuffed with functionality. It’s a nice package that contains the minimal amount of code to be useful but not overengineered.
That said, sometimes you need more. First off, what about different distributions? The normal distribution is a pretty common necessity. random-fu
really shines in this domain. You’ll have to pull in the rvar
package as well to run this next example, which will print out a random number pulled from a normal distribution with mean 100
and a standard deviation of 5
:
import Control.Monad
import System.Random
import Data.Random
import Data.RVar
import Control.Monad.State
normalNumber :: State StdGen Double
normalNumber = sampleRVar (normal 100 5)
main :: IO ()
main = do
gen <- newStdGen
print (evalState normalNumber gen)
Notice the State
pattern from earlier. Also, there a bunch of common distributions that ship with random-fu
.
One last thing I should mention is that we’re not tied to StdGen
, the RNG that ships with random
.
In fact, it does not have strong statistical properties, and should probably be avoided for many “real” applications (See this reddit post, and thank you to reddit user tom-md for the note!).
There are faster and more stable ones ones, like PureMT
from random-source
or TFGen
from tf-random
. These are both instances of RandomGen
, so you can plug either one of those in wherever you saw the generic type signature RandomGen g => ...
in this post. For example, mixing PureMT
back into MonadRandom
:
import Control.Monad.Random
import Data.Random.Source.PureMT
dieRoll :: RandomGen g => Rand g Int
dieRoll = getRandomR (1, 6)
main :: IO ()
main = newPureMT >>= print . evalRand dieRoll
Is there anything else you’d like to know about randomness in Haskell? Let me know in the comments!
Ben
]]>I realized this morning that I don’t have links to any of this external-but-relevant stuff anywhere on the site, and I want to change that just to keep everything in one place!
Last year, I wrote a guest post on Stack Overflow’s hiring blog about my preferred technical interviewing strategy. You can read about the project walkthrough technique here.
I have a YouTube channel! I ran a series called “Breakfast Bash” here containing little blurbs about basic computer science concepts. There’s also a three hour Haskell workshop VOD put together by me and Matt Parsons. You can see that here.
I gave a talk at the Developers of Athens Meetup Group about Lockhart’s Lament and how it applies to programming. The slides are here; I would love to give this talk again to a larger crowd.
That’s all for now! Just some stuff I wanted to consolidate.
]]>I’m working on a tile laying game! It’s called Trash Kings. Here’s the premise:
It’s drop-off day at the dump. As a scavenging rodent, you must do what ever it takes to earn your claim over the wealth of new garbage. Will you gather the best waste and earn the title of Trash King?
Here’s what the prototype looks like:
In Trash Kings, players place tiles on an expanding board in order to create and claim mounds. Players will gain points based on mounds they claim throughout the game. The player with the most points at the end wins!
The game draws a lot of inspiration from Carcassonne, but there are a few mechanics that make it feel unique. In Trash Kings, you have the option of swapping tiles around on the game board in order to create areas to control.
Certain configurations of tiles prevent other players from swapping out important tiles on the game board, and using these is the key to beating out your opponents. Also, the way the game is scored during and at the end of the game differs, necessitating the use of multiple strategies during the course of the game.
You can download the rulebook here, and a print & play version of the game here.
Trash Kings is very much a work in progress. Every time I have played it (with my wife, or alone as multiple players) I tweak something about it. I’m open to any and all feedback, which can be sent to bkovach13@gmail.com or left as a comment on this blog post.
Enjoy!
]]>Today we finished up the longest flight of our lives! 12 hours to Paris, then 2 more into Venice with hardly any rest. We made our way to the hotel via a bus ride and a water bus (vaporetto) ride through the Grand Canal. Fewer people speak English than we expected, but we’re getting by. Thanks Duolingo! Our B&B hosts at Dimora Marciana are all really nice, and gave us food recommendations and a walking route to get a feel for the city. After crashing for about an hour in the hotel room, we ate at one of the recommended restaurants. I accidentally ordered something with nuts, so Amanda and I ended up having to switch dishes, leaving her with a sad couscous salad, and I ate the delicious cannelloni she ordered. Oops! Otherwise, it was pretty good! After lunch, we set off on the recommended walking route, which took us through San Marco, the Accademia district, Dorsoduro, over the Rialto bridge and back to our hotel. Very pretty and maze-like with lots of interesting shops along the way. Amanda remembers nothing from that walk, since she was a sleep-deprived zombie. Thank goodness for pictures! We stopped briefly for some cappuccinos at a cafe (which we later learned was a faux pas- no milk after breakfast!). We were hungry for dinner around 5:30, but no restaurants are open between 2:30 and 6:30… so we ate gelato for dinner! Heading in for an early bedtime (like 6:00 PM) because we are both exhausted.
We slept like babies! And breakfast at Dimora Marciana was so good! Excellent cappuccinos and a variety of pastries, meats, cheeses and croissants. Every espresso drink here is perfect! How do they do it?? After breakfast, we set out for Piazza San Marco with an audio tour we’d downloaded onto our phones. This was a good idea, because it pointed out a lot of interesting subtle details about the piazza that we wouldn’t have noticed otherwise. For example, the surrounding buildings are built with three completely separate styles of column, because the floors were built in different eras. Visual stories are also told on the columns beneath Doge’s Palace (the dwelling place of the historic ruler of Venice) - very cool. We saw a carving story of a couple meeting, courting, marrying, conceiving a child, hugging their child, finding out the child is sick, and then burying their child…Poor little statue family :( After the audio tour, we went into Doge’s Palace, which has been turned into a museum. Every ceiling is painted beautifully; it’s incredible. The transition from the palace to the jails (over the Bridge of Sighs) is a surreal experience. It’s a weird couple of steps from gilded halls to drab, thick stone walls and plain prison cells. When we were done, we walked around town a little and found a place to eat. I had seafood risotto (with lots of seafood I’d never seen before!) and Amanda had some salmon pasta. Both were delicious. We took an afternoon break and waited for a vegetarian place to open for dinner. We wandered around for an hour and a half through the rainy alleyways, dodging beggar ladies and getting chased by a man who wanted to sell us a rose. It was really dark and creepy, and the restaurant was hard to find. When we finally found it and then waited for them to open, they were completely booked! Huge bummer, but we went to a place called Tuttinpietti instead for some pasta, which we ate in our room. It was great anyway, so whatever. Afterwards, we set back out for a gelato place called SuSo, determined to mark off a win for the night. We got super lost, and by the time we found it, it had just closed! Agh! We went back to our rooms, tired and gelato-less, ate some chocolate raisins that we had brought on the plane for dessert and went to bed. Oh well! Dessert before dinner tomorrow! Eventually we’ll figure out this meal thing…
We had planned to tour the islands (Murano and Burano) today, but we woke up groggy and postponed. Instead, we looked into touring Peggy Guggenheim’s old house-turned-museum. It was a very interesting place with exciting exhibits of modern art. Also, Gwendoline Christie (Brienne of Tarth from Game of Thrones) was there with us! Crazy! After the tour, we wandered around the Accademia district and made our way all the way to “the point” at the end of the island. It was a foggy, chilly day, but it was still very beautiful and peaceful. After a mediocre lunch at a touristy place, we rested for a bit. A little later on, we went to a place called I Tre Mercanti for some tiramisu (dessert before dinner!). It was so good that we inhaled it (literally…it was hard not to choke on all the cocoa powder on top)! Acqua Alta Libraria was in the same part of town, so we checked it out. This place must have been the coolest bookstore on the planet. The building is full of books from floor to ceiling, piled on tables and old gondolas. We grabbed a couple of Christmas gifts here and headed back to the hotel room, still very tired from jet lag and the last few days. Looking forward to the Murano/Burano trip tomorrow!
We got a good night’s rest last night and headed out to get on a boat to Burano island late in the morning. The walk was nice - the area near Fondamente Nove (the water bus station) was relaxed. We hopped on a water bus and passed a few islands - a walled city, and Murano, the glass blowing island. The travel time to Burano was pretty long (about 50 minutes) but we eventually made it to the island of lace and colorful houses! It was kind of like a very-tiny Venice with a small fraction of the population. We sought out the lace museum, which was a disappointment, but only 5 euros each, so whatever. It turns out that the history of lace is super boring! But there were old women from Burano making lace there, which was a fascinating process! We expected to spend more time there, so we had some downtime for a coffee (coffee in Italy means a single shot of espresso) at a local bar before our lunch reservation. We also walked around the island for a while, saw the leaning clock tower, and relaxed by the seaside. Lunch was intense. We went to a place called Gatto Nero and both had a portion of seafood spaghetti; it was the most involved meal I’ve ever eaten, with several shellfish and other seafood. It was super tasty. The second course was a plate of seafood fritto misto. Some of that was good and some of it wasn’t. I liked this part a lot more than Amanda did. The best part of the plate was fried salted cod. We split a bottle of tasty white wine with this meal, too, then capped off the 2 hour long lunch with a shot of espresso. It was getting late in the day at this point, but we wanted to see Murano, so we got on the next water taxi and headed that way. We browsed a couple of shops here and picked up some glass creations. The glass here was stunning; I wish we’d been able to stay a bit longer, but it was getting late and dark. For the first time, we understood why dinner here is served so late- after that huge, long lunch, we were not expecting to be hungry until at least 8:00! When we got back to mainland Venice, we stopped by a shop for a hot chocolate, which is more like a shot of warm chocolate than the kind we’re used to. Something from the day ended up making Amanda sick that night, unfortunately. :( Her sickness was so rough, that she got actual whiplash from throwing up so hard. She keeps calling it “power puking”…gross.
A day of rest! Mandy needed some recovery time from last night, so we didn’t do a whole lot. We did get some delicious fresh pasta from a place called Dal Moro’s for lunch, though. I spent a bit of the day planning for what to do in Florence. We rallied a little later to go grab a slice of pizza for dinner at Antico Forno. Venice was much busier on a Friday night, and there was even a (very good!) violinist playing music in one of the squares. The pizza, the walk, and the sparkling Christmas lights strung all around were really nice. A magical way to end our time in Venice!
This was the day we left Venice. We walked up to Rialto Bridge with our packed luggage and took a water bus to the train station. It was a beautiful day, so we sat in front, on the exterior of the boat. This is maybe my favorite part about Venice -boating down the Grand Canal is absolutely stunning! Once we made it to the train station, we had to gather our bearings. Neither of us had ever ridden on an EU train (or any passenger train?) before, so we weren’t sure of what to do. We eventually found our train and our seats and settled in. Well, so we thought - we were wrong! At the second stop, some Italian women kicked us out of our seats and we moved to the proper section of the train. After that minor speed bump, the ride was comfortable and quick. The destination train station in Florence was really overwhelming, though. The largest crowds we encountered in Venice were miniscule compared to the ones we found in Florence! We searched around everywhere for a bathroom in a huge crowd and had to ask a few people how to find the bus we were looking for. It all worked out, but there were thousands of people in the station and our bus came really late. Eventually it did come, and after a 20 minute bus ride, we arrived at our destination - the base of the hil our hotel sat on. Trudging up the hill with our heavy luggage on our backs was so hard after such an exhausting day, but finally arriving at the hotel in the Tuscan hills was a huge relief. We got a brief tour of the grounds at Villa Agape and crashed for a little while. We had booked a wine tour for that night, though, so we couldn’t relax for too long! We were also starving after traveling with no lunch and were desperate to eat. So, we had our hotel’s shuttle drive us into Florence and we found a place to eat (hot pots with dumplings for a little variety; not that good!) and then made our way to Ponte Vecchio afterwards. Ponte Vecchio is the oldest bridge in Florence, and the meetup point for the start of our wine tour. Turns out, we were the only ones who booked the tour, so we got a surprise private tour of the city! We got some little wine glasses to carry around town and were shown some interesting landmarks (mostly little historic things about the city). As for wine, we tasted 5 kinds and ate some crostini. Amanda was still queasy about drinking white wine, but I enjoyed all of it! The most interesting story was the one about the “black rooster” emblem on Chianti wine bottles. We ended the tour at the duomo - a huge, beautiful, green and white church that took 150 years to complete! Then we got picked up by our hotel shuttle and crashed hard.
This was our food tour day, so we woke up extra early to meet with the tour guide at 9:30! We ate a tiny bite to eat at the hotel so we would be hungry for the tour and got the earliest shuttle ride possible to meet up in time. We told the shuttle driver to pick us up at 6:30 that evening, since we anticipated doing some museum tours and shopping after the 4-hour food tour. Sadly, once we rushed across town to get there in time, we suddenly realized…nobody else was there. We had done all that and we had shown up for the tour on the wrong day! Major oops! After kicking ourselves (and laughing a lot) for messing up so badly, we decided to make the most of the day anyway. We headed to the Uffizi gallery first. Wow, that museum is huge! There were a ton of paintings and statues - just the amount of art in one place was impressive. The most popular parts were the few Da Vinci and Michelangelo paintings. I didn’t get a picture, but I also loved the shield with medusa’s head painted on it. We found a little sandwich shop called Antico Noe and had some roast beef paninis on a little back road afterwards (we were starving! We were expecting a food tour!). Afterwards, we found a little pastry shop and split a chocolate ganache tart with some coffee. Oooh yummy! The never-ending bakery options were heaven to Amanda! Then we wandered around looking for the Christmas market. We thought we found it, but what we found was actually a tiny little market square, with some church bake sale going on. Eventually, we wandered into the real Christmas market, which is significantly bigger and in a large square. It’s mostly German food and imported things. A little underwhelming.
We still had half a day left before the shuttle was coming back, so we decided to visit the Boboli Gardens. These are the expansive sculpture gardens behind the Pitti Palace, which was home to Cosimo I of the Medici family. Holy cow, that place is massive! And beautiful, too! We saw a man feeding some ducks and pigeons in a pond, and a happy kid playing with the pigeons - a couple of nice, memorable moments. Along with the Boboli Gardens ticket, we got access to a costume museum, which we visited after exhausting ourselves in the gardens. The old clothing was neat, especially the clothes that the royal family (Cosimo I and Eleanora) had been buried in, although it was a bit morbid. Afterwards, we popped into a pastry shop for a cannoli - Amanda had been looking for a good one. We split it and it was really yummy! I wish we had gotten two. For dinner, Mandy was feeling a little homesick and found some American place called The Diner. It was weird to see a depiction of US culture in Italy - they had paintings of historical desegregation moments and fat people on the walls! I had some particularly odd pureed chicken soup, and Amanda ate a burger and fries. The burger wasn’t bad! Not long after we ate, we got picked up and went to bed around 8 PM. This was one of the most exhausting days of my life. We walked a total of 10 miles, and boy were our legs sore!
We slept well last night. We had to get up early today to catch the real food tour, which was rough but we managed. We ate a little more this morning than yesterday, because it was painful to deprive ourselves of delicious free breakfast two days in a row, and we headed out to our first stop - a butcher shop! There were wild boar heads on the wall, sausages hanging from the ceiling, and tons of prosciuttos curing on the walls. It was like nothing I’ve ever seen before. There were seven of us on the tour - four from Wisconson and one other guy from Suwanee, GA (where I grew up)! Small world! Everyone was really nice, including our amazing tour guide, Martina. We started off by tasting a few prosciuttos, one cured with only salt and one with black pepper added. We went to a bakery next. We had some coccoli (a savory fried bread donut thing) with mozzarella and tomato inside. The next stop was the big one - the central market - a giant, two story farmer’s market. We ate some sandwiches with boiled brisket on the bottom floor and took a short break for some coffee and shopping upstairs. After the break, we hit up a pastry shop for some cream pasteries (very big buttery custard danish-type things) and headed to an enoteca - a wine bar - to have some crostini and wine. This place was small but packed full of wine, oil, balsamic vinegar and a variety of cheeses for sale. Here I learned that real balsamic vinegar is incredible, but expensive. It costs 90 euros for a small bottle, fermented and bottled by the Modena government. Olive oil in Italy also has a much stronger flavor, which is quite nice. All of the crostini and appetizer-type things we ate were good as well, except the “special treat” of a liver-based topping, Crostini Toscani, which we didn’t like. The wines we tasted here were good, but the real stars of this stop were the oil and vinegar, surprisingly! The last stop on the tour was a chocolate shop called Vestri. The family who owns this place controls every step of the chocolate production, from the cacao farm to finished chocolate. The chocolate was some of the best we’ve ever had; I tried a sipping chocolate “shot” with some anice liqueur. Yum. We then parted ways. I highly recommend doing one of these food tours in Florence; it was probably our favorite part of the whole trip.
We had two hours before our shuttle was scheduled to pick us up, so we decided to check out the Museo Galilei before heading back to the hotel. This was a very interesting museum, containing lots of old scientific tools. One of the most impressive was a three dimensional “map of the universe” that took 5 years to complete. Lots of other hidden treasures in here as well. I’m glad we went; it was a nice break from art museums, but it was torture that we couldn’t play with the amazing tools and devices! Afterwards, we waited for our shuttle for 15 minutes before realizing…they weren’t coming. We had to find a phone to use to call the hotel. We walked and walked, looking for pay phones, which were all BROKEN, or tourism offices, which were completely unhelpful! Eventually, we came across another hotel and asked the concierge (named Eliza - We’ll never forget her because it felt like she was saving our lives!!) to help us out. She got a hold of the shuttle and we were able to finally get picked up soon after. We were stranded for a minute, though, after an extremely long day! I was getting very cranky. We rested for a few hours before heading down to San Miniaio Square for dinner. We ate pizza with delicious Italian toppings like truffles and prosciutto. It was lovely. It’s supposed to rain tomorrow but we’re going to try to see David and finish our Christmas shopping. Other than that, we’ll try to relax and get ready for the trip home. Amanda has been nocturnal since her night up with food poisoning, and her neck still can’t turn, so we will try visiting a pharmacy tomorrow.
We did get some relaxation in today! Breakfast was good, but Amanda tried to get by on cheese and honey (a weird choice). We slept in a bit and headed out of the hotel around 10:00. We immediately headed towards the Accademia Gallery, where David is located. After waiting in a line for about 20 minutes, we made it in. Everyone and their mother told us to pre-purchase a ticket online to skip the waiting part. We should have listened, but it could have been much worse! The museum isn’t very big; it’s just a single corridor and a few rooms. The main attraction really is David. It is hugely impressive, though. We both found the full-size statue remarkable. We spent probably 20 minutes there, staring at the giant statue and browsing some of Michelangelo’s unfinished sculptures. Afterwards, it was shopping day! First, we found a reputable leather shop and got Amanda a new purse. This place was in the market square, so we picked up some veggie samosas from a hole in the wall Indian restaurant for lunch while we were in the area. The next stop was the huge central market for food-related gifts! Here, we got some cheese, oil, vinegar and coffee for our friends and family. We also caved and bought some traditional balsamic vinegar for ourselves. The last stop we made was at Massimo Ravinale for some silk; the man who helped us out was notably very helpful. After a quick last-minute cannoli (we can’t stop eating dessert in between every meal!), we went back to the hotel and packed some stuff up in preparation for tomorrow. And we binge watched Please Like Me (it’s an Australian show on European Netflix here). It was a nice stretch of relaxation we’d been needing. At 7:30, we went to dinner at a place called Zeb, which was recommended by our food tour guide. We had two types of ravioli (pear-peccorino and yellow pumpkin with butter sauce), a peppery beef stew called peposo, a few glasses of wine and a salted chocolate torte. It was the best meal we had in Italy. A perfect way to end our trip to Florence! We leave in the morning (at 5:00 AM) and will really miss it here. The whole experience was unforgettable!
]]>This is the second in a series of posts about category theory and its relationship with Haskell. We’ll be exploring monoids in this post!
A monoid is a mathematical object, just like a category, which we explored in the last post. Intuitively, a monoid is a structure which encodes the ability to concatenate two things, and a zero-like element which, when used in concatenation, does not affect the other element. Let’s look at a typical mathematical formulation of a monoid:
A monoid is a set M equipped with a binary operation × : m → m → m and a special element 1 ∈ M such that 1 and × satisfy the following laws:
I wouldn’t be surprised at this point if, even without a heavy mathematical background, one could come up with an example of a monoid. Let’s look at a couple.
The monoid {ℝ, ×, 1} is an obvious example of a monoid; that is, our set M is the real numbers, our binary operation is multiplication, and our 1 element is the number 1.
Another example of a monoid on numeric values is {ℤ, +, 0}; the integers under addition with 1 being 0.
In the programming world, monoids show up all over the place as well. A well-known monoid, using haskell syntax, is {[a], (++), []}
– the monoid of lists under concatentation with 1 being the empty list.
As a haskell programmer, the aforementioned definition for a monoid looks awfully familiar. The typeclass for Monoid
looks almost the exact same, sans axioms, and modulo names, as the traditional mathematical formulation:
class Monoid m where
mempty :: m -- Analogous to 1
mappend :: m -> m -> m -- Analogous to our binary operation
We can even encode the three examples of monoids mentioned above in a very straightforward way:
-- Let's pretend Floats are the reals
-- {R, *, 1}
newtype Product = Product{ runProduct :: Float }
instance Monoid Product where
mempty = Product 1
mappend (Product a) (Product b) = Product (a * b)
-- {Z, +, 0}
newtype Sum = Sum{ runSum :: Integer }
instance Monoid Sum where
mempty = Sum 0
mappend (Sum a) (Sum b) = Sum (a + b)
-- This one is defined in the Prelude
-- {[a], ++, []}
instance Monoid [a] where
mempty = []
mappend xs ys = xs ++ ys
We have to verify that our instances follow the laws, which translated to haskell look like this:
-- Associativity
(a `mappend` b) `mappend` c == a `mappend` (b `mappend` c)
-- Left/right unit laws
mempty `mappend` a == a == a `mappend` mempty
It is not so hard to reason through these laws for the instances listed above.
We’re not done quite yet, because category theorists have picked up on a quaint definition of a monoid:
A monoid is a category with one object.
Well, that’s convenient. But what is really going on here? Let’s look at a visual representation of the category theory interpretation of {ℤ, +, 0} that we saw before*:
* The image gets a little messy with the negatives, but those are valid arrows as well.
Morphisms in our category are elements of ℤ. This can be confusing if we have some expectation about the way arrows should look. But, recall that to define a morphism, we only need to be able to assign a source and target to them, which are totally abstract concepts. Our set of morphisms is then exactly ℤ, where we just pick the same source and target for every single element.
id is the arrow labeled 0:
Here are two more arrows:
Composing these arrows gives us something that looks like this, intuitively:
Composition is addition, so this is equivalent to:
An important thing to note here is that the object doesn’t really matter. The above diagrams should make sense regardless of what the source and target of the arrows are, as long as they’re the same thing. The source and target must be the same so we can compose freely. If they weren’t, we wouldn’t be guaranteed to “have access” to the start of any arrow in our monoid at the end of any other arrow, which would disallow composition. This means we necessarily only have a single object in our category.
So long as we have a monoid by the classical definition, we have a monoid by the category theoretical definition. The conceptual mapping of formulations (from classical to category theoretical) is:
The tricky thing here is that in category-theory-land, the morphisms might not really look like functions, or even arrows, as we might expect. An integer as a category morphism follows the laws if we plug in the right id and ∘, and that’s all that matters.
Let’s formulate the aforementioned Sum
as a legitimate haskell Category
(C
for Category
):
data SumC a b where
SumC :: Integer -> SumC a a
deriving instance Show (SumC a b)
instance Category SumC where
id = SumC 0
SumC x . SumC y = SumC (x + y)
We can use GADTs to encode the monoid (read: category with a single object) of integers under addition. We are saying two things here:
SumC
is constructed by an Integer
(effectively, this means that a SumC
is an Integer
; this is just a wrapper).SumC
, we can only construct things of type SumC a a
.In effect, if we view SumC
as cat
(the type of morphisms in a category), we can encode the monoid {ℤ, +, 0} exactly as a Category
. If you look closely, you’ll notice that we only have a single object in our category, and we don’t care what it is. This is exactly a
. A SumC
can only be constructed in one way, namely, by asserting that its source and target is a
. The identity arrow is 0
, which we’d expect, and composing two arrows is adding their Integer
values.
The category laws all hold, which you may check. This instance acts exactly like the builtin Monoid
instance for the Sum
we saw previously, modulo operator/function names*:
*Main> SumC 8 . id
SumC 8
*Main> id . SumC 8
SumC 8
*Main> SumC 17 . SumC 90
SumC 107
* s/id/mempty, s/./+, s/SumC/Sum and you’ve got the original form.
Just like with a Category
, this isn’t the only Monoid
you can encode – you should be able to use this type of construction for any haskell Monoid
.
Note that I wouldn’t actually recommend doing this in practice; it’s mainly a mental exercise. That said, I think it’s pretty cool that we can get very close to a category-theoretic formulation of what a monoid is in Haskell!
]]>