在没有不安全的情况下杂耍存在

时间:2019-06-30 17:27:52

标签: haskell existential-type higher-rank-types

最近我一直在玩这种类型的游戏,我理解它是自由分布仿函数的编码(有关切向背景,请参见this answer):

data Ev g a where
    Ev :: ((g x -> x) -> a) -> Ev g a

deriving instance Functor (Ev g)

存在构造函数可确保我只能通过提供多态提取器Ev g来消耗forall x. g x -> x,并且可以为自由构造的提升和降低功能指定兼容类型:

runEv :: Ev g a -> (forall x. g x -> x) -> a
runEv (Ev s) f = s f

evert :: g a -> Ev g a
evert u = Ev $ \f -> f u

revert :: Distributive g => Ev g a -> g a
revert (Ev s) = s <$> distribute id

但是,尝试赋予Ev g一个Distributive实例很困难。假设Ev g最终只是一个具有奇怪参数类型的函数,可能希望仅对函数进行distribute的线程处理(相当于(??) :: Functor f => f (a -> b) -> a -> f b from lens,而不会通过Ev包装器以任何方式检查参数类型:

instance Distributive (Ev g) where
    distribute = Ev . distribute . fmap (\(Ev s) -> s)

但是,这不起作用:

Flap3.hs:95:53: error:
    • Couldn't match type ‘x’ with ‘x0’
      ‘x’ is a rigid type variable bound by
        a pattern with constructor:
          Ev :: forall (g :: * -> *) x a. ((g x -> x) -> a) -> Ev g a,
        in a lambda abstraction
        at Flap3.hs:95:44-47
      Expected type: (g x0 -> x0) -> a
        Actual type: (g x -> x) -> a
    • In the expression: s
      In the first argument of ‘fmap’, namely ‘(\ (Ev s) -> s)’
      In the second argument of ‘(.)’, namely ‘fmap (\ (Ev s) -> s)’
    • Relevant bindings include
        s :: (g x -> x) -> a (bound at Flap3.hs:95:47)
   |
95 |     distribute = Ev . distribute . fmap (\(Ev s) -> s) 
   |                                                     ^
Failed, no modules loaded.

GHC反对重新包装存在物,即使我们在重新包装和重新包装之间不做任何事情。我发现的唯一出路是求助于unsafeCoerce

instance Distributive (Ev g) where
    distribute = Ev . distribute . fmap (\(Ev s) -> unsafeCoerce s)

或者,以更加谨慎的方式进行拼写:

instance Distributive (Ev g) where
    distribute = eevee . distribute . fmap getEv
        where
        getEv :: Ev g a -> (g Any -> Any) -> a
        getEv (Ev s) = unsafeCoerce s
        eevee :: ((g Any -> Any) -> f a) -> Ev g (f a)
        eevee s = Ev (unsafeCoerce s)

如果没有unsafeCoerce,是否可以解决此问题?还是真的没有其他方法?

其他说明:

  • 我相信Ev是我可以赋予结构的最正确的类型,尽管我很乐意被证明是错误的。我所有将量词转移到其他地方的尝试都导致需要在其他地方使用unsafeCoerce或导致evertrevert的类型不允许它们组成。

  • 乍一看,这种情况类似于this blog post by Sandy Maguire中描述的情况,该情况最终也坚持使用unsafeCoerce


以下为Ev g提供Representable实例的做法可能会使问题更加明显。 As dfeuer notes,这实际上是不可能的;毫不奇怪,我不得不再次使用unsafeCoerce

-- Cf. dfeuer's answer.
newtype Goop g = Goop { unGoop :: forall y. g y -> y }

instance Representable (Ev g) where
    type Rep (Ev g) = Goop g
    tabulate f = Ev $ \e -> f (Goop (goopify e))
        where
        goopify :: (g Any -> Any) -> g x -> x
        goopify = unsafeCoerce
    index (Ev s) = \(Goop e) -> s e

虽然goopify确实令人震惊,但我认为这里有一个安全的理由。存在编码意味着传递给包装函数的任何e必定是元素类型上的提取多态,而专门针对Any的提取器只是因为我要求这样做。因此,forall x. g x -> xe的明智类型。之所以需要专门针对Any的舞步只是用unsafeCoerce立即撤消舞步,是因为GHC迫使我通过做出选择来摆脱存在。在这种情况下,如果我忽略了unsafeCoerce,将会发生以下情况:

Flap4.hs:64:37: error:
    • Couldn't match type ‘y’ with ‘x0’
      ‘y’ is a rigid type variable bound by
        a type expected by the context:
          forall y. g y -> y
        at Flap4.hs:64:32-37
      Expected type: g y -> y
        Actual type: g x0 -> x0
    • In the first argument of ‘Goop’, namely ‘e’
      In the first argument of ‘f’, namely ‘(Goop e)’
      In the expression: f (Goop e)
    • Relevant bindings include
        e :: g x0 -> x0 (bound at Flap4.hs:64:24)
   |
64 |     tabulate f = Ev $ \e -> f (Goop e) 
   |                                     ^
Failed, no modules loaded.

Prolegomena需要在此处运行代码:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}

import Data.Distributive
import Data.Functor.Rep
import Unsafe.Coerce
import GHC.Exts (Any)

-- A tangible distributive, for the sake of testing.
data Duo a = Duo { fstDuo :: a, sndDuo :: a }
    deriving (Show, Eq, Ord, Functor)

instance Distributive Duo where
    distribute u = Duo (fstDuo <$> u) (sndDuo <$> u)

2 个答案:

答案 0 :(得分:2)

每个Distributive函子都可以做成Representable,尽管我们无法在Haskell中证明这一点(我想这不是建设性的)。但是解决您的问题的一种方法是只切换类。

newtype Evv f a = Evv
  {unEvv :: forall g. Representable g
         => (forall x. f x -> g x) -> g a}

instance Functor (Evv g) where
  fmap f (Evv q) = Evv $ \g -> fmap f (q g)

evert :: g a -> Evv g a
evert ga = Evv $ \f -> f ga

revert :: Representable g => Evv g a -> g a
revert (Evv f) = f id

newtype Goop f = Goop
  {unGoop :: forall x. f x -> x}

instance Distributive (Evv g) where
  collect = collectRep

instance Representable (Evv g) where
  type Rep (Evv g) = Goop g
  tabulate f = Evv $ \g -> fmap (\rg -> f (Goop $ \fx -> index (g fx) rg)) $ tabulate id
  index (Evv g) (Goop z) = runIdentity $ g (Identity . z)

我还没有直接使用Distributive进行尝试(正如HTNW所建议的那样),但是如果由于某种原因根本不可能,我不会感到惊讶。

警告:我尚未证明这实际上是免费的Representable

答案 1 :(得分:1)

尽管unsafeCoerce仍然是必要的,但danidiaz和dfeuer的建议使我有了更整齐的编码:

{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}

import Unsafe.Coerce
import GHC.Exts (Any)
import Data.Distributive
import Data.Functor.Rep

-- Px here stands for "polymorphic extractor".
newtype Px g = Px { runPx :: forall x. g x -> x }

newtype Ev g a = Ev { getEv :: Px g -> a }
    deriving Functor

runEv :: Ev g a -> (forall x. g x -> x) -> a
runEv s e = s `getEv` Px e

evert :: g a -> Ev g a
evert u = Ev $ \e -> e `runPx` u

revert :: Distributive g => Ev g a -> g a
revert (Ev s) = (\e -> s (mkPx e)) <$> distribute id
    where
    mkPx :: (g Any -> Any) -> Px g
    mkPx e = Px (unsafeCoerce e) 

instance Distributive (Ev g) where
    distribute = Ev . distribute . fmap getEv

instance Representable (Ev g) where
    type Rep (Ev g) = Px g
    tabulate = Ev 
    index = getEv

在我的x原始表述中,Ev变量被普遍量化;我只是伪装成功能箭头后面的存在。尽管该编码可以在不使用revert的情况下编写unsafeCoerce,但将负担转移到了实例实现上。在这种情况下,直接使用通用量化最终会更好,因为它将魔术集中在一个地方。

此处的unsafeCoerce技巧与问题中的tabulate所展示的是相同的:x中的distribute id :: Distributive g => g (g x -> x)专用于Any,然后在fmap下,unsafeCoerce将立即撤消专业化。我相信这个技巧是安全的,因为我可以控制unsafeCoerce的内容。

关于摆脱unsafeCoerce,我确实看不到办法。问题的一部分是,我似乎需要某种形式的强制性类型,因为unsafeCoerce技巧最终等于将forall x. g (g x -> x)变成g (forall x. g x -> x)。为了进行比较,我可以使用强制性类型功能的子集编写一个模糊的类似(如果更简单)的场景,该子集将落入有争议的ExplicitImpredicativeTypes扩展范围内(请参阅GHC ticket #14859和链接其中进行讨论):

{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RankNTypes #-}

newtype Foo = Foo ([forall x. Num x => x] -> Int)

testFoo :: Applicative f => Foo -> f Int
testFoo (Foo f) = fmap @_ @[forall x. Num x => x] f 
    $ pure @_ @[forall x. Num x => x] [1,2,3]
GHCi> :set -XImpredicativeTypes 
GHCi> :set -XTypeApplications 
GHCi> testFoo @Maybe (Foo length)
Just 3

distribute id[1,2,3]棘手。在id :: g x -> g x中,我要保持量化的类型变量出现在两个地方,其中之一是distribute(->) (g x)仿函数)的第二个类型参数。至少在我未经训练的眼睛看来,这完全是棘手的。