我有一个动态值缓存。其中一些类型为Delayed a
。
通常当我访问缓存时,我知道类型a
,所以它不是问题,我可以使用fromDynamic
强制转换为Maybe a
。
我想调用一个函数,它不需要知道a
列表中Dynamic
类型的任何信息。 (方法是cancel :: Delay a -> IO ()
)。
有办法吗?
基本上我需要一种方法从Dynamic
到Forall a . Delayed a
?
有关信息,Delayed保持挂起的异步值和MVar以启动或取消它。它相当于
data Delayed m a = Delayed { blocker :: MVar Bool, async :: Async m a }
这些值存储在缓存中(使用Dynamic并存储其他内容)。显示缓存状态时,我需要能够获得Delayed
值的状态(涉及访问阻止程序但与实际值无关。
答案 0 :(得分:6)
类型forall a . X a
的值是可以实例化为X Int
,X Bool
,X 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 HRefl
和App
的第一个参数实际上是相同的类型。
此时,编译器知道某些Delayed
的{{1}}。因此,您可以在值a ~ Delayed x
上调用函数x
。它还必须提供forall a . Typeable a => Delayed a -> r
为x :: 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
。)