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.
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
execute for things that will modify database
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
query_ here because
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
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
PostgreSQL.Simple to pass database connections
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
ReaderT, rather than explicitly redefining each and
every type signature of each function that uses the
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
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
asking 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