2

我正在尝试在 Haskell 中实现一个 DelayedJob 端口(来自 Rails 世界)。

这是我拥有的类型类,它代表一个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

以下是我打算如何使用它:

createJob :: (DelayedJob j r) => j -> AppM JobId

我一直在编写一个相当通用的invokeJob函数,该函数将从jobs表中读取一行,查看jobs.jobtype列并调用版本的正确runJob版本(即runJob属于正确类型类实例的函数)。

我有以下内容,但它充满了样板:

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

本质上,有没有办法deserialise在运行时动态地约束函数的具体类型,而不必编写这么多样板文件?

4

2 回答 2

1

我不愿意建议它,但恕我直言,这里要做的“正确”事情是放弃类型安全性并带有一点运行时多态性。而不是类型类,有一种不透明Handler的,在你的情况下,它可能类似于Value -> AppM ().

只需将它们放在一个Map Type Handler或类似的东西中,您可以动态更新它们;Type这就是你用来区分你的工作类型的东西(即一些 sum 类型,或者,为了最小的类型安全,只是 a )String。当你拉出一个工作时,你可以在列表中查找合适的处理程序;处理程序是不透明的,并且知道它负责哪些类型,因此用户端不需要知道如何反序列化值。

这不是很聪明,但应该可以工作。

于 2017-07-05T12:37:19.133 回答
1

我想知道这样的事情是否可行。我不知道您正在使用的框架。

let go :: forall jobtype . Job -> JobID -> ....
    go job jid =
       case deserialise (job ^. jobdata) :: Either String jobtype of
          Left e -> error e
          Right j -> storeJobResult jid $ runAppM j
case (job ^. jobtype) of
     SyncContacts -> go @ SynContactsJob job jid
     SendEmail    -> go @ SendEmail job jid
     ...

否则,我会尝试看看单身人士是否有帮助。如果您不想启用模糊类型和类型应用程序,也可以使用代理。

于 2017-07-05T08:47:38.733 回答