最近我一直在玩这种类型的游戏,我理解它是自由分布仿函数的编码(有关切向背景,请参见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
或导致evert
和revert
的类型不允许它们组成。
乍一看,这种情况类似于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 -> x
是e
的明智类型。之所以需要专门针对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)
答案 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)
仿函数)的第二个类型参数。至少在我未经训练的眼睛看来,这完全是棘手的。