Haskell反思中的黑魔法

时间:2013-07-22 17:13:01

标签: haskell reflection

我希望有人可以对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
  1. 我无法解析reify的定义。也许我错过了一些关于评估顺序的简单方法,但看起来unsafeCoerce::a->b应用于三个参数。
  2. unsafeCoerce中使用的同构类型是什么?
  3. k
  4. 的定义中实际评估的函数reify在哪里?
  5. Reifes的任何实例在哪里?例如,我可以在GHCi中运行以下行,只加载Data.Reflection和Data.Proxy(并设置-XScopedTypeVariables):.

    reify (3::Int) (\(_:: Proxy q) -> print $ reflect (Proxy :: Proxy q))

  6. 在哪里/什么是幻影的具体类型?

  7. newtype Magic中的“魔力”是什么?

2 个答案:

答案 0 :(得分:22)

在了解此实现之前,您应该了解API。最初的想法(反映了对类型级别的任意指针)在this paper中进行了解释,该slowreflection版本的unsafeCoerce :: a -> b中实现。所以我假设你已经知道它是如何工作的,以及如何使用API​​。 “fast”是同一个API的另一个实现,它使用一些特定于GHC的技巧来加快速度。所以:

  1. 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)

  2. 嘿。 “同构”

    更严重的是:理解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

  3. k unsafeCoerce (Magic k),只是使用其他类型。因此函数k是函数Proxy,其类型已被修改。它的功能完全相同。

  4. 让我们考虑如何编译这个类(我将使用大写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
  5. 现在我们可以看到它是如何工作的:

    :: a获取两个参数,值:: forall s. Reifies s a => Proxy s -> r和函数Magic。由于函数的形状与:: Magic a r的形状相同,因此我们将其转换为值Magic a r。在操作上,forall s. (Proxy s -> a) -> Proxy s -> runsafeCoerce大致相同,因此我们可以交叉并(Proxy s -> a) -> Proxy s -> r。当然,a -> rconst a同构,所以我们只需要传递正确的函数(Proxy)和{{1}}值,我们就完成了。

  6. 魔术一直在你的功能中。

答案 1 :(得分:1)

shachaf 的回答不仅比我的好,而且也是正确的,但是我在这里为后人保留我的想法。


这比我高,但无论如何这是我的答案。

  1. Magic是2个参数的构造函数,unsafeCoerce正在强制Magic k r -> Magic a r,类型为const a。它在这里做的是强制一个函数从一种类型到另一种类型。所以我怀疑unsafeCoerce的第三个'论点'实际上是传递给unsafeCoerce (Magic k :: Magic a r) (const a)
  2. 结果的论证
  3. 看起来大多数黑魔法都没有定义Reifies的实例,构造函数使用存在类型。存在类型实际占用的方式似乎是通过unsafeCoerce
  4. 发生的
  5. 它在构造函数的coersion中定义为与const a类型相同。这一点,我至少理解。
  6. 据我所知,实例并不存在 - 存在类型似乎被unsafeCoerce说服类型系统所占据,无论是否存在任何实例,类型值{ {1}}已创建。无论这种类型是否存在,它似乎已经“伪造”存在,如伪造,而不是金属加工。
  7. 我认为Magic a r的第一个参数是Magic的具体类型。
  8. newtype Proxy中的神奇之处在于:Magic这就是(forall (s :: *). Reifies s a => Proxy s -> r)包裹的东西的类型。它指定MagicProxy的参数是s的存在类型,或者可以这么说,类型系统知道它的唯一事实是它是{的一个实例{1}}。所以它知道无论Reifies是什么,它都必须是Reifies的一个实例,并且它可以使用该实例,但就是它可以做的全部。就像类型系统已经清除了该类型的所有其他知识一样,并且只知道s所持有的值,它可以在其上使用函数Reifies - 就是这样。但Proxy恢复了值和类型,因为类型在reflect中编码而reflect似乎只是函数Magic的{​​{1}}。或者至少,这就是我收集的内容。
  9. 这可能是完全错误的,但这似乎是碎片粘在一起的方式。或者至少,它对我来说有多大意义。