我希望有人可以对Data.Reflection中的黑魔法略有启发。相关的片段是:
{-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE KindSignatures #-}
module Data.Reflection
(
Reifies(..)
, reify
) where
import Data.Proxy
import Unsafe.Coerce
class Reifies s a | s -> a where
-- | Recover a value inside a 'reify' context, given a proxy for its
-- reified type.
reflect :: proxy s -> a
newtype Magic a r = Magic (forall (s :: *). Reifies s a => Proxy s -> r)
-- | Reify a value at the type level, to be recovered with 'reflect'.
reify :: forall a r. a -> (forall (s :: *). Reifies s a => Proxy s -> r) -> r
reify a k = unsafeCoerce (Magic k :: Magic a r) (const a) Proxy
reify
的定义。也许我错过了一些关于评估顺序的简单方法,但看起来unsafeCoerce::a->b
应用于三个参数。unsafeCoerce
中使用的同构类型是什么?k
reify
在哪里?
Reifes
的任何实例在哪里?例如,我可以在GHCi中运行以下行,只加载Data.Reflection和Data.Proxy(并设置-XScopedTypeVariables):.
reify (3::Int) (\(_:: Proxy q) -> print $ reflect (Proxy :: Proxy q))
在哪里/什么是幻影的具体类型?
newtype Magic
中的“魔力”是什么?答案 0 :(得分:22)
在了解此实现之前,您应该了解API。最初的想法(反映了对类型级别的任意指针)在this paper中进行了解释,该slow在reflection
版本的unsafeCoerce :: a -> b
中实现。所以我假设你已经知道它是如何工作的,以及如何使用API。 “fast”是同一个API的另一个实现,它使用一些特定于GHC的技巧来加快速度。所以:
b
确实应用于三个参数,这意味着unsafeCoerce
必须是双参数函数的类型。特别是,此Magic a r -> (Proxy s -> a) -> Proxy s -> r
的类型类似于:class Num a where
plus :: a -> a -> a
negate :: a -> a
foo :: Num a => a -> a
foo x = plus x (negate x)
。
嘿。 “同构”
更严重的是:理解GHC的类型类实现很重要,这涉及字典传递。当你有像
这样的东西data Num a = Num { plus :: a -> a -> a, negate :: a -> a }
foo :: Num a -> a -> a
foo dict x = plus dict x (negate dict x)
它被翻译成类似
的东西foo
GHC根据您使用newtype
时的类型确定要传入的字典。注意单参数函数如何变成双参数函数。
因此类型类的实现是传递额外的字典参数。但请注意,作为优化,当类只有一个方法时,我们可以使用data
而不是class Default a where
def :: a
doubleDef :: Default a => (a, a)
doubleDef = (def, def)
。 E.g。
newtype Default a = Default { def :: a }
doubleDef :: Default a -> (a, a)
doubleDef dict = (def dict, def dict)
变成
def
但生成unsafeCoerce
的操作Magic k
。
k
unsafeCoerce (Magic k)
,只是使用其他类型。因此函数k
是函数Proxy
,其类型已被修改。它的功能完全相同。
让我们考虑如何编译这个类(我将使用大写P
切换到class Reifies s a | s -> a where
reflect :: Proxy s -> a
foo :: Reifies s a => ...
以简化操作。)
newtype Reifies s a = Reifies { reflect :: Proxy s -> a }
foo :: Reifies s a -> ...
-- which is unsafeCoerce-compatible with
foo :: (Proxy s -> a) -> ...
变成
newtype Magic a r = Magic (forall s. Reifies s a => Proxy s -> r)
所以,在操作上,
unsafeCoerce
是newtype Magic a r = Magic (forall s. (Proxy s -> a) -> Proxy s -> r)
- 与
reify
现在我们可以看到它是如何工作的:
:: a
获取两个参数,值:: forall s. Reifies s a => Proxy s -> r
和函数Magic
。由于函数的形状与:: Magic a r
的形状相同,因此我们将其转换为值Magic a r
。在操作上,forall s. (Proxy s -> a) -> Proxy s -> r
与unsafeCoerce
大致相同,因此我们可以交叉并(Proxy s -> a) -> Proxy s -> r
。当然,a -> r
与const a
同构,所以我们只需要传递正确的函数(Proxy
)和{{1}}值,我们就完成了。
魔术一直在你的功能中。
答案 1 :(得分:1)
shachaf 的回答不仅比我的好,而且也是正确的,但是我在这里为后人保留我的想法。
这比我高,但无论如何这是我的答案。
Magic
是2个参数的构造函数,unsafeCoerce
正在强制Magic k
r -> Magic a r
,类型为const a
。它在这里做的是强制一个函数从一种类型到另一种类型。所以我怀疑unsafeCoerce
的第三个'论点'实际上是传递给unsafeCoerce (Magic k :: Magic a r) (const a)
Reifies
的实例,构造函数使用存在类型。存在类型实际占用的方式似乎是通过unsafeCoerce
。const a
类型相同。这一点,我至少理解。unsafeCoerce
说服类型系统所占据,无论是否存在任何实例,类型值{ {1}}已创建。无论这种类型是否存在,它似乎已经“伪造”存在,如伪造,而不是金属加工。Magic a r
的第一个参数是Magic
的具体类型。Proxy
中的神奇之处在于:Magic
这就是(forall (s :: *). Reifies s a => Proxy s -> r)
包裹的东西的类型。它指定Magic
,Proxy
的参数是s
的存在类型,或者可以这么说,类型系统知道它的唯一事实是它是{的一个实例{1}}。所以它知道无论Reifies
是什么,它都必须是Reifies
的一个实例,并且它可以使用该实例,但就是它可以做的全部。就像类型系统已经清除了该类型的所有其他知识一样,并且只知道s
所持有的值,它可以在其上使用函数Reifies
- 就是这样。但Proxy
恢复了值和类型,因为类型在reflect
中编码而reflect
似乎只是函数Magic
的{{1}}。或者至少,这就是我收集的内容。这可能是完全错误的,但这似乎是碎片粘在一起的方式。或者至少,它对我来说有多大意义。