I'm trying to implement a port of DelayedJob (from the Rails world) in Haskell.
Here's the typeclass I have which represents a DelayedJob
class (FromJSON j, ToJSON j, FromJSON r, ToJSON r) => DelayedJob j r | j -> r where
serialise :: j -> Value
serialise = toJSON
deserialise :: Value -> Either String j
deserialise = parseEither parseJSON
runJob :: j -> AppM r
Here's how I plan to use it:
createJob :: (DelayedJob j r) => j -> AppM JobId
I'm getting stuck with writing a fairly general invokeJob
function which will read a row from the jobs
table, look at the jobs.jobtype
column and invoke the correct version of the runJob
version (i.e. the runJob
function belonging to the correct type-class instance).
I have the following, but it is full of boilerplate:
data JobType = SyncContacts | SendEmail | SendSms deriving (Eq, Show, Generic)
invokeJob :: JobId -> AppM ()
invokeJob jid = do
job <- fetchJob jid
case (job ^. jobtype) of
SyncContacts -> case (deserialise (job ^. jobdata) :: Either String SynContactsJob) of
Left e -> error e
Right j -> storeJobResult jid $ runAppM j
SendEmail -> case (deserialise (job ^. jobdata) :: Either String SendEmailJob) of
Left e -> error e
Right j -> storeJobResult jid $ runAppM j
SendSms -> case (deserialise (job ^. jobdata) :: Either String SendSms) of
Left e -> error e
Right j -> storeJobResult jid $ runAppM j
Essentially, is there a way to constrain the concrete type of the deserialise
function dynamically during runtime without having to write so much boilerplate?
I am loath to suggest it, but IMHO the "right" thing to do here is to forego type safety with a little runtime polymorphism. Instead of a type class, have an opaque
Handler
of sorts, in your case it'd probably be something likeValue -> AppM ()
.Just put these in a
Map Type Handler
or something along those lines, which you can update dynamically; wherebyType
is what you use to distinguish your job types (i.e. some sum type or, for minimum type safety, just aString
). When you pull out a job you can just look up the appropriate handler in the list; The handler is opaque and knows which types it is responsible for, so the user end doesn't need to know how to deserialize values.This isn't very clever but should work nevertheless.