Notes from Two Weeks of Haskell
I’ve been writing Haskell at $WORK
for about two weeks
now, and it’s been pretty fun. I’ve also learned quite a bit. This is
just a place to store some common idioms/things I’ve learned, and mostly
to have a place where I can put some simple, explicit code samples.
PostgreSQL.Simple
PostgreSQL.Simple is a great package started by Bryan O’Sullivan, the guy that primarily wrote Real World Haskell. He also wrote the Aeson library. They have similarities, and it’s pretty obvious.
This section is mostly because I keep forgetting (ie. haven’t practiced enough) how to put things into queries, get things out of the database, serialize them to records, that sort of thing.
Updating a Record
Use execute
for things that will modify database
structures, like UPDATE
or INSERT
. You’ll
notice that here we don’t have to specify the types on
(name, lat, lng, session_id)
because they exist in the
function declaration and GHC can infer them.
update_location_query = "UPDATE location AS loc \
\SET name = ?, lat = ?, lng = ? \
\FROM session AS sesh \
\WHERE sesh.location_id = loc.id AND \
\ sesh.id = ?;"
updateLocation :: T.Text -> T.Text -> Double -> Double -> ReaderT Connection IO Int64
updateLocation session_id name lat lng = do
conn <- ask
lift $ execute conn update_location_query (name, lat, lng, session_id)
Selecting a Bunch of Records
We use query_
here because query_
doesn’t
expect any arguments to interpolate into the SQL query.
class_query = "SELECT id, name \
\FROM class \
\ORDER BY name;"
getClasses :: Connection -> IO [Class]
getClasses conn = query_ conn class_query
Selecting Just One Record
This will return a list of one item, but Haskell doesn’t know that so it comes back as a list. It works well enough. This also interpolates the class ID into the query.
session_query = "SELECT id, timestamp \
\FROM session \
\WHERE cls_id = ? \
\ORDER BY timestamp DESC \
\LIMIT 1;"
getSessionsOfClass :: Connection -> Class -> IO [Session]
getSessionsOfClass conn cls =
query conn session_query class_id
where class_id = (Only $ classId cls) :: Only UUID
ReaderT
ReaderT
is really useful, and a great introduction (for me) on how to use a
monad transformer stack. I puzzled out a trivial example of using it
with PostgreSQL.Simple
to pass database connections
around.
These code samples are what I’m actually using. Here a
Connection
record is embedded inside the
ReaderT
context so we can use it later on, without
explicitly passing around a Connection
object. This doesn’t
have much benefit now, but later on if we need to add extra
functionality it will be trivial to rewrite the sections of code using
the ReaderT
, rather than explicitly redefining each and
every type signature of each function that uses the
Connection
.
main :: IO ()
main = do
conn <- connectToDev
args <- getArgs
case parseArgs args of
Just (session_id, address) ->
flip runReaderT conn $ do
startGeocode (T.pack session_id) (T.pack address)
...
and an example of unwrapping the context of the
ReaderT
:
updateLocation :: T.Text -> T.Text -> Double -> Double -> ReaderT Connection IO Int64
updateLocation session_id name lat lng = do
conn <- ask
lift $ execute conn update_location_query (name, lat, lng, session_id)
Here you can see that we’re getting the Connection
out
of the ReaderT
by ask
ing for it. Neat! Also of
note here, is that you have to lift
the result of the
execute
call back into the monad transformer stack. Fun
fact here: Because our transformer stack is only one level deep, you can
use lift
and liftIO
interchangably.