Haskell:执行Data.Dynamic中包含的IO操作

时间:2015-02-19 04:35:57

标签: haskell dynamic

假设我有一个Data.Dynamic.Dynamic对象,它包装了一个IO动作(对于某些可能未知的IO a来说,类型为a。我觉得我应该能够执行此IO操作并获得其结果,包含在Dynamic(其类型为a)中。有没有标准的库函数可以做到这一点? (类似于dynApply,但是对于IO动作性能而不是函数应用程序。)

该函数的实现可能看起来像

dynPerform :: Dynamic -> Maybe IO Dynamic 
dynPerform (Dynamic typ act)
   = if (typeRepTyCon typ) /= ioTyCon then Nothing else Just $
       do result <- (unsafeCoerce act :: IO Any)
          return Just . Dynamic (head $ typeRepArgs typ) $ result

exampleIOAction = putChar
typeOfIOAction  = typeOf exampleIOAction
ioTyCon         = typeRepTyCon typeOfIOAction

但显然这是使用了几个不安全的操作,所以我宁愿从库中取出它。 (事实上​​,由于Data.Dynamic.Dynamic类型的不透明性,我所写的不会在Data.Dynamic之外工作。)

1 个答案:

答案 0 :(得分:2)

我不相信你可以放心地做你想做的事。让我建议一种替代方法。


也许幽灵类型可以帮到你。假设您提供某种cron作业服务,用户每隔x微秒执行一次操作,用户可以随时查询该操作的最新运行结果。

假设您自己可以访问以下原语:

freshKey :: IO Key
save :: Key -> Dynamic -> IO ()
load :: Key -> IO (Maybe Dynamic)

您应该安排作业并制定计划来存储结果,同时您仍然在类型系统中“知道”该行为的类型。

-- do not export the internals of PhantomKey
data PhantomKey a = PhantomKey {
  getKey :: Key
  getThread :: Async ()
}

-- This is how your user acquires phantom keys;
-- their phantom type is tied to the type of the input action
schedule :: Typeable a => Int -> IO a -> IO (PhantomKey a)
schedule microseconds m = do
  k <- freshKey
  let go = do
        threadDelay microseconds
        a <- m
        save k (toDyn a)
        go
  thread <- async go
  return $ PhantomKey k thread

unschedule :: PhantomKey a -> IO ()
unschedule pk = cancel (getThread pk)

-- This is how your user uses phantom keys;
-- notice the function result type is tied to the phantom key type
peekLatest :: PhantomKey a -> IO (Maybe a)
peekLatest pk = load (getKey pk) >>= \md -> case md of
  Nothing -> return Nothing -- Nothing stored at this key (yet?)
  Just dyn -> case fromDynamic dyn of
    Nothing -> return Nothing -- mismatched data type stored at this key
                              -- hitting this branch is probably a bug
    Just a -> return (Just a)

现在,如果我是您的API用户,我可以将它与您自己的数据类型一起使用,只要它们是可输入的:

refreshFoo :: IO Foo

main = do
  fooKey <- schedule 1000000 refreshFoo
  -- fooKey :: PhantomKey Foo
  mfoo <- peekLatest fooKey
  -- mfoo :: Maybe Foo

那我们取得了什么成就?

  • 您的图书馆正在接受用户IO操作,并在任意时间点执行
  • 您的图书馆正在通过动态blob保存您的用户数据
  • 您的图书馆正在通过动态blob
  • 加载您的用户数据

所有这一切都没有您的图书馆了解您的用户的数据类型。

在我看来,如果你将知道的内容放入动态blob中的IO动作,那么你在类型系统中丢失了关于那个东西的信息在您应该使用所述类型信息的上下文中。 TypeRep可以让你在值级别输入信息,但是(据我所知)不能将这些信息重新映射到类型级别。