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 structures, like
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
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 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
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
Connection out of 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 use