[英]How to convert Dynamic to Forall something
我有一个动态值缓存。 其中一些类型为Delayed a
。
通常当我访问缓存时,我知道类型a
,所以这不是问题,我可以使用fromDynamic
转换为Maybe a
。
我想调用一个函数,它不需要知道关于Dynamic
列表中的类型a
任何信息。 (方法是cancel :: Delay a -> IO ()
)。 有办法吗?
基本上我需要一种从Dynamic
到Forall a . Delayed a
Forall a . Delayed a
?
有关信息,Delayed保持挂起的异步值和MVar以启动或取消它。 它相当于
data Delayed m a = Delayed { blocker :: MVar Bool, async :: Async m a }
这些值存储在缓存中(使用Dynamic并存储其他内容)。 当显示缓存状态时,我需要能够获得Delayed
值的状态(涉及访问阻止程序但与实际值无关)。
值类型为forall a . X a
forall a . X a
是一个值,可以实例化为X Int
, X Bool
, X String
等中的任何一个。据推测,您的缓存存储了许多不同类型的值,但没有一个值对每个可能的类型参数都有效。 你真正需要的是一个类型的值exists a . Delayed a
exists a . Delayed a
但是,Haskell没有一流的存在量词,因此您必须以某种方式编码该类型。 一种特殊的编码是:
castToDelayed :: (forall a . Typeable a => Delayed a -> r) -> Dynamic -> r
假设你有这个功能; 那么你可以简单地编写castToDelayed cancel :: Dynamic -> IO ()
。 请注意, castToDelayed
的函数参数提供了一个Typeable
约束,但您可以自由地忽略该约束(这是cancel
正在执行的操作)。 另请注意,由于单独的类型,此函数必须是部分的(显然并非每个Dynamic
都是某个a的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
),以及其类型的表示(类型为TypeRep a
)。
它在TypeRep a
上模式匹配以确定它是否是应用程序(使用App
)。 显然, Delayed a
是类型构造函数的应用程序,因此这是我们必须检查的第一件事。
它将类型构造函数( App
的第一个参数)与对应于Delayed
的TypeRep
进行比较(注意,您必须instance Typeable Delayed
设置一个instance Typeable Delayed
)。 如果该比较成功,则它在证明(即Just HRefl
)上的模式匹配,即App
和Delayed
的第一个参数实际上是相同的类型。
此时,编译器知道某个x
a ~ Delayed x
x
。 所以,你可以调用函数forall a . Typeable a => Delayed a -> r
forall a . Typeable a => Delayed a -> r
在值x :: a
上forall a . Typeable a => Delayed a -> r
。 它还必须提供证据,证明x
是Typeable
,这是类型的值精确给出TypeRep x
- withTypeable
具体化这个值水平证明作为一个类型级约束(或者,你可以有输入功能需要作为参数TypeRep a
,或者只是完全省略约束,因为您的特定用例不需要它;但这种类型是最普遍的可能)。
在旧版本中,原理基本相同。 但是, TypeRep
当时没有采用类型参数; 你可以在它的模式匹配,发现如果是TypeRep
相应Delayed
,但你不能证明存储内部值编译器Dynamic
的类型是Delayed x
的一些x
。 因此,在将函数k
应用于值x
的步骤中,它将需要unsafeCoerce
。 此外,在GHC 8.2之前没有withTypeable
,所以你必须编写带有类型的函数(forall a . Delayed a -> r) -> Dynamic -> r
(幸运的是,这对你的用例来说已经足够了); 或者自己实现这样的函数(请参阅函数的来源以了解如何;在旧版GHC上的实现将类似,但将具有类型TypeRep -> (forall a . Typeable a => Proxy a -> r) -> r
反而)。
以下是GHC <8.2(在8.0.2上测试)中的实现方法。 这是一个可怕的黑客,我没有声称它会在任何情况下都正确。
{-# 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
,但我们假设它的定义如下,用于测试目的:
data Delayed a = Some a | None deriving (Typeable, Show)
然后考虑这个简单的测试用例:
test0 :: Typeable a => Delayed a -> String
test0 (Some x) = maybe "not a String" id $ cast x
test0 None = "None"
test0' =
let c = castToDelayed test0 in
[ c (toDyn (None :: Delayed Int))
, c (toDyn (Some 'a'))
, c (toDyn (Some "a")) ]
为什么不定义
{-# LANGUAGE ExistentialQuantification #-}
data Delayed' = forall a. Delayed' (Delayed a)
然后存储比在Dynamic
? 然后,您可以cast
其从动态的case
中cancel
,并将结果传递给cancel
。 (根据您的使用情况,您甚至可能不再需要Dynamic
。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.