Черная магия в отражении Хаскелла
Я надеялся, что кто-то может пролить свет на черную магию в 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))
-
Где/что такое тип phantom reified?
- Что такое "волшебство" в
newtype Magic
?
Ответы
Ответ 1
Прежде чем понимать эту реализацию, вы должны понимать API. Исходная идея (отражающая произвольный указатель на уровень типа) объясняется в этой статье, которая реализована в slow "версии reflection
. Поэтому я предполагаю, что вы уже знаете, как это работает, и как используется API." fast "- это еще одна реализация того же API, которая использует несколько специфический для GHC трюк, чтобы ускорить процесс. Итак:
-
unsafeCoerce :: a -> b
действительно применяется к трем аргументам, что означает, что b
должен быть типом функции с двумя аргументами. В частности, тип этого unsafeCoerce
выглядит примерно так: Magic a r -> (Proxy s -> a) -> Proxy s -> r
.
-
Хех. "Изоморфными".
Более серьезно: важно понять реализацию GHC классов типов, которая включает в себя передачу словаря. Когда у вас есть что-то вроде
class Num a where
plus :: a -> a -> a
negate :: a -> a
foo :: Num a => a -> a
foo x = plus x (negate x)
Он переводится во что-то вроде
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)
С помощью GHC выясняется, какой словарь должен проходить в зависимости от типа при использовании foo
. Обратите внимание, как функция с одним аргументом превратилась в функцию с двумя аргументами.
Таким образом, реализация класса типа заключается в передаче дополнительного словарного аргумента. Но учтите, что в качестве оптимизации мы можем использовать newtype
вместо data
, когда класс имеет только один метод. Например.
class Default a where
def :: a
doubleDef :: Default a => (a, a)
doubleDef = (def, def)
превращается в
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
значение, и мы закончили.
-
Магия всегда была в вашей функции.
Ответ 2
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
, убеждая систему типов, что независимо от того, существуют ли какие-либо экземпляры, значение типа Magic a r
был создан. Независимо от того, существует ли этот тип или нет, он, похоже, "подделан", как в подделке, а не как в металлообработке.
- Первый параметр
Magic
- это тип reified как Proxy
, я думаю.
- Магия в newtype
Magic
находится в этом бите: (forall (s :: *). Reifies s a => Proxy s -> r)
То, что тип вещи Magic
обертывается. Он указывает, что аргумент Proxy
, s
является экзистенциальным типом Reifies
, или, так сказать, единственным фактом, о котором знает система типов, является то, что он является экземпляром Reifies
. Поэтому он знает, что независимо от s
, это должен быть экземпляр Reifies
, и он может использовать этот экземпляр, но все, что он может сделать. Это похоже на то, что система типов очистила все другие знания о том, что такое тип, и знает только, что для значения, которое имеет значение Proxy
, он может использовать на нем функцию reflect
- и это. Но reflect
восстанавливает значение и тип, потому что тип был закодирован в Magic
, а reflect
- просто unsafeCoerce
функции const a
. Или, по крайней мере, это то, что я собираю.
Это может быть совершенно неправильно, но похоже, что куски склеиваются. Или, по крайней мере, как это имело смысл для меня.