This blog runs on a barebones blogging framework that I knocked together using Yesod 1.1 back in 2013. I recently ported it over to Yesod 1.4. Apart from the few changes that I have detailed below, everything worked straight away. Refactoring code in Haskell is a very different experience compared to fully dynamic languages.
Here are some notes on the changes that I encountered between Yesod 1.1 and 1.4. Perhaps these will be useful for someone.
aformM Link to heading
Previously I used aformM to get the current time in a form:
commentForm :: EntryId -> Form Comment
commentForm entryId = renderDivs $ Comment
pure entryId
aformM (liftIO getCurrentTime)
areq textField (fieldSettingsLabel MsgCommentName) Nothing
aopt emailField (fieldSettingsLabel MsgCommentEmail) Nothing
aopt urlField (fieldSettingsLabel MsgCommentUrl) Nothing
areq htmlField (fieldSettingsLabel MsgCommentText) Nothing
pure False <* recaptchaAForm
Now, use lift (liftIO getCurrentTime):
commentForm :: EntryId -> Form Comment
commentForm entryId = renderDivs $ Comment
pure entryId
-- aformM (liftIO getCurrentTime)
lift (liftIO getCurrentTime)
areq textField (fieldSettingsLabel MsgCommentName) Nothing
aopt emailField (fieldSettingsLabel MsgCommentEmail) Nothing
aopt urlField (fieldSettingsLabel MsgCommentUrl) Nothing
areq htmlField (fieldSettingsLabel MsgCommentText) Nothing
pure False <* recaptchaAForm
MinLen Link to heading
Some new names clash with the Prelude, e.g. maximum is not the usual function from the Prelude, but rather something from Data.MinLen that encodes type-level natural numbers.
*Main> :t maximum
maximum :: MonoFoldableOrd mono => MinLen (Succ nat) mono -> Element mono
*Main> :t P.maximum
P.maximum :: Ord a => [a] -> a
No unKey or PersistInt64 Link to heading
Persistent values in the old system looked like this:
Entity {entityKey = Key {unKey = PersistInt64 1},
entityVal = title: "first post" mashed title: "first-post" year: 2015 month: 8 day: 14 content: "Hi there!" visible: False}
Entity {entityKey = Key {unKey = PersistInt64 2},
entityVal = title: "second post" mashed title: "second-post" year: 2015 month: 8 day: 14 content: "Hi there! Do de dah!" visible: False}
and we could use PersistInt64 to construct the value, or unKey to deconstruct it.
*Main> :t PersistInt64
PersistInt64 :: GHC.Int.Int64 -> PersistValue
*Main> :t unKey
unKey :: KeyBackend backend entity -> PersistValue
Now values look like:
Entity {entityKey = EntryKey {unEntryKey = SqlBackendKey {unSqlBackendKey = 1}}, entityVal = "first post"}
Entity {entityKey = EntryKey {unEntryKey = SqlBackendKey {unSqlBackendKey = 2}}, entityVal = "second post"}
Old code like
foo :: PersistValue -> GHC.Int.Int64
foo (PersistInt64 i) = i
niceEntryId :: KeyBackend backend entity -> String
niceEntryId x = show $ foo $ unKey x
becomes
niceEntryId :: Key Entry -> Text
niceEntryId = DT.pack . show . unSqlBackendKey . unEntryKey
We could also use toPathPiece:
*Main> :t toPathPiece :: Key Entry -> Text
toPathPiece :: Key Entry -> Text :: Key Entry -> Text
If you’re wondering how to find such a thing, look at the output of :info Key in ghci which includes these lines:
instance PathPiece (Key User) -- Defined at Model.hs:10:1
instance PathPiece (Key Entry) -- Defined at Model.hs:10:1
instance PathPiece (Key Comment) -- Defined at Model.hs:10:1
I believe that the more general option is fromSqlKey:
unKey' :: ToBackendKey SqlBackend record => Key record -> Text
unKey' = DT.pack . show . fromSqlKey
This should work over any SQL backend, unlike the older code that was tied to the particular implementation (e.g. 64bit ints).
Similarly, old code constructed a Key from an integer:
let entryId = Key $ PersistInt64 (fromIntegral i)
New code uses toSqlKey since the PersistInt64 constructor isn’t available:
let entryId = toSqlKey i :: Key Entry
Links Link to heading
Original blog framework, written with Yesod 1.1.9.4: https://github.com/carlohamalainen/cli-yesod-blog.
New blog framework, compiles against Yesod 1.4: https://github.com/carlohamalainen/cli-yesod-blog-1.4.