如何将Dynamic转换为Forall的东西

时间:2017-08-15 14:57:43

标签: haskell

我有一个动态值缓存。其中一些类型为Delayed a

通常当我访问缓存时,我知道类型a,所以它不是问题,我可以使用fromDynamic强制转换为Maybe a

我想调用一个函数,它不需要知道a列表中Dynamic类型的任何信息。 (方法是cancel :: Delay a -> IO ())。 有办法吗?

基本上我需要一种方法从DynamicForall a . Delayed a

修改

有关信息,Delayed保持挂起的异步值和MVar以启动或取消它。它相当于

 data Delayed m a = Delayed { blocker :: MVar Bool, async :: Async m a }

这些值存储在缓存中(使用Dynamic并存储其他内容)。显示缓存状态时,我需要能够获得Delayed值的状态(涉及访问阻止程序但与实际值无关。

2 个答案:

答案 0 :(得分:6)

类型forall a . X a的值是可以实例化为X IntX BoolX String等任何值的值。据推测,您的缓存存储的值很多不同类型,但没有单个值在每个可能的类型参数中有效。您实际需要的是exists a . Delayed a类型的值。但是,Haskell没有一流的存在量词,因此您必须以某种方式编码该类型。一种特殊的编码是:

castToDelayed :: (forall a . Typeable a => Delayed a -> r) -> Dynamic -> r

假设你有这个功能;然后你可以简单地写castToDelayed cancel :: Dynamic -> IO ()。请注意,castToDelayed的函数参数提供了Typeable约束,但您可以自由地忽略该约束(这是cancel正在做的事情)。另请注意,由于单独的类型,此函数必须是部分的(显然并非每个Dynamic对于某些Delayed a都是a),因此在实际代码中,您应该生成例如而是Maybe r。在这里,我将忽略这个细节,然后抛出一个错误。

您实际编写此函数的方式取决于您使用的GHC版本(最新版本,8.2版本或某些旧版本)。在8.2,这是一个非常好的,简单的功能:

{-# LANGUAGE ViewPatterns #-}
-- NB: probably requires some other extensions

import Data.Dynamic
import Type.Reflection

castToDelayed :: (forall a . Typeable a => Delayed a -> r) -> Dynamic -> r
castToDelayed k (Dynamic (App (eqTypeRep (typeRep :: TypeRep Delayed) -> Just HRefl) a) x)
  = withTypeable a (k x) 
castToDelayed _ _ = error "Not a Delayed"

(旁白:起初我认为Con模式同义词在这里很有用,但是在深入检查时它似乎完全没用。你必须改用eqTypeRep。)

简而言之,此功能的工作原理如下:

  • 它在Dynamic值上进行模式匹配,以获取存储在其中的实际值(存储量化类型a),以及其类型的表示形式{{1} })。

  • 它在TypeRep a上匹配模式以确定它是否是应用程序(使用TypeRep a)。显然,App是类型构造函数的应用程序,因此这是我们必须首先检查的内容。

  • 它将类型构造函数(Delayed a的第一个参数)与App对应的TypeRep进行比较(请注意,此处必须有Delayed) 。如果该比较成功,则在证明(即instance Typeable Delayed)上模式匹配,Just HReflApp的第一个参数实际上是相同的类型。

  • 此时,编译器知道某些Delayed的{​​{1}}。因此,您可以在值a ~ Delayed x上调用函数x。它还必须提供forall a . Typeable a => Delayed a -> rx :: a的证据,该值由x类型的值精确给出 - Typeable将此值级证明作为类型级别进行处理约束(或者,您可以将输入函数作为参数TypeRep x,或者只是省略约束,因为您的特定用例不需要它;但这种类型是最常用的)。

在旧版本中,原理基本相同。但是,withTypeable没有采用类型参数;您可以对其进行模式匹配,以发现它是否与TypeRep a对应的TypeRep,但您无法向编译器证明TypeRep中存储的值具有类型Delayed某些Dynamic。因此,在您将函数Delayed x应用于值x的步骤中,需要unsafeCoerce。此外,在GHC 8.2之前没有k,所以你必须编写类型为x的函数(幸运的是,这对你的用例来说已经足够了);或者自己实现这样一个函数(参见函数的source来看看如何; GHC旧版本的实现将类似,但将改为withTypeable类型。

以下是如何在GHC< 8.2(在8.0.2上测试)。这是一个可怕的黑客攻击,我并没有声称它在任何情况下都能正确使用。

(forall a . Delayed a -> r) -> Dynamic -> r

我不知道TypeRep -> (forall a . Typeable a => Proxy a -> r) -> r实际上是什么,但我们假设它的定义如下:

{-# LANGUAGE DeriveDataTypeable, MagicHash, ScopedTypeVariables, PolyKinds, ViewPatterns #-}

import Data.Dynamic
import Data.Typeable
import Unsafe.Coerce
import GHC.Prim (Proxy#)
import Data.Proxy

-- This part reifies a `Typeable' dictionary from a `TypeRep'.
-- This works because `Typeable' is a class with a single field, so 
-- operationally `Typeable a => r' is the same as `(Proxy# a -> TypeRep) -> r'
newtype MagicTypeable r (kp :: KProxy k) =
  MagicTypeable (forall (a :: k) . Typeable a => Proxy a -> r)

withTypeRep :: MagicTypeable r (kp :: KProxy k)
            -> forall a . TypeRep -> Proxy a -> r
withTypeRep d t = unsafeCoerce d ((\_ -> t) :: Proxy# a -> TypeRep)

withTypeable :: forall r . TypeRep -> (forall (a :: k) . Typeable a => Proxy a -> r) -> r
withTypeable t k = withTypeRep (MagicTypeable k) t Proxy

-- The type constructor for Delayed
delayed_tycon = fst $ splitTyConApp $ typeRep (Proxy :: Proxy Delayed)

-- This is needed because Dynamic doesn't export its constructor, and
-- we need to pattern match on it.
data DYNAMIC = Dynamic TypeRep Any

unsafeViewDynamic :: Dynamic -> DYNAMIC
unsafeViewDynamic = unsafeCoerce

-- The actual implementation, much the same as the one on GHC 8.2, but more 
-- 'unsafe' things
castToDelayed :: (forall a . Typeable a => Delayed a -> r) -> Dynamic -> r
castToDelayed k (unsafeViewDynamic -> Dynamic t x) =
  case splitTyConApp t of
    (((== delayed_tycon) -> True), [a]) ->
      withTypeable a $ \(_ :: Proxy (a :: *)) -> k (unsafeCoerce x :: Delayed a)
    _ -> error "Not a Delayed"

然后考虑这个简单的测试用例:

Delayed

答案 1 :(得分:2)

为什么不定义

{-# LANGUAGE ExistentialQuantification #-}

data Delayed' = forall a. Delayed' (Delayed a)

然后存储而不是Dynamic?然后,您可以cast将其case从动态cancel上移除,并将结果传递给Dynamic。 (根据您的使用案例,您甚至可能不再需要UPDATE ow_data D LEFT JOIN (SELECT * FROM ow_data WHERE id_sensor=14 GROUP BY date_in ORDER BY date_in DESC) X ON D.date_in > X.date_in SET D.cons = X.value WHERE D.id_sensor=14 。)