[Haskell-cafe] Instantiating a typeclass based on a runtime value?

Matt parsonsmatt at gmail.com
Wed Jul 5 17:08:25 UTC 2017

You are asking GHC to select an instance of a type class (a compile-time
operation) at run-time. I believe that this is possible with something like
the exinst[1] package and runtime dictionaries with the constraints[2]
package, but you're getting into some fairly hairy territory. I don't think
that this would end up providing a nice UX for library consumers, either,
as you end up needing user-defined singleton types to lift that information
into the type level -- eg, a sum type of operations that the user supports,
and then an open data family (or GADT) indexed by that sum type. In order
for runJob to know about it, the class definition needs to know about that
index, which kind of ruins the point of the class -- if you're going to
keep a closed type of operations, you might as well just have:

data Job = SyncContact SyncContactJob | ImportFoo ImportFooJob

data SyncContactJob = SyncContactJob { userId :: UserId }
data ImportFooJob = ImportFooJob { fooId :: FooId }

runJob :: Job -> App ()

The main benefit to the existentialized approach, IMO, is when the actions
are entirely derived from type class operations. Sandy Maguire gave a good
talk on the approach at Lambdaconf [3]. Since much of the behavior here is
ad hoc, then I think you'll get less utility out of the class.

We have a similar sort of framework on our projects at work, but using
Amazon SQS messages instead of a database. The function has a signature

pollSqsFor :: FromJSON a => SqsQueue -> (a -> App b) -> App ()

This lets us write `pollSqsFor ImportThing (thingImporter :: ThingRequest
-> App ())` for the workers that end up processing it, with the polling
function handling stuff like delay time, error handling, keeping the
message invisible, deleting it if successful, etc. This sort of thing would
be easy to port to using a database, the only difference being loading only
matching rows from the database for the given type.

[1] exinst: https://hackage.haskell.org/package/exinst
[2] contraints:
[3] some1 like you: http://reasonablypolymorphic.com/some1-like-you/#/title

Matt Parsons

On Wed, Jul 5, 2017 at 6:56 AM, Saurabh Nanda <saurabhnanda at gmail.com>

> Overall context: I'm trying to port the DelayedJob library from
> Ruby/Rails world to Haskell. The basic idea is to serialise a job and write
> it to the DB, deserialise it, and run the job using a job-runner function,
> which is determined by the job-type.
> I'm using a type-class to define the custom job-runner, something on the
> lines of:
>     class (FromJSON j, ToJSON j) => DelayedJob j where
>       runJob :: j -> AppM ()
>     data SyncContactsJob = SyncContactsJob { userId :: UserId } deriving
> (Eq, Show, Generic, FromJSON, ToJSON)
>     instance DelayedJob SyncContactsJob where
>       runJob job = do
>         -- entire job execution logic comes here
> Is there **any** type-system hackery, that will let me take a runtime
> value, eg. "SyncContactsJob", "DoBackupJob", and use that to run the
> correct version of the `runJob` function? That is,  how do I write the
> following function:
>    invokeJob :: JobId -> AppM ()
>    invokeJob jid = do
>        jobRow <- fetchJob jid
>        let jtype = jobRow ^. jobtype -- this will have "SyncContactsJob"
>             jvalue = jobRow ^. jobdata -- this will have a Data.Aeson.Value
>        runJob (......) -- QUESTION: What do I write here to make this
> compile?
> _______________________________________________
> Haskell-Cafe mailing list
> To (un)subscribe, modify options or view archives go to:
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
> Only members subscribed via the mailman list are allowed to post.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20170705/bcd731f3/attachment.html>

More information about the Haskell-Cafe mailing list