如何在存在包装器下应用任意函数?

时间:2015-05-06 03:03:07

标签: haskell existential-type

我正在尝试编写一个函数(此处称为hide),它可以在存在的包装器中应用足够多态的函数(或者提升函数来处理具有隐藏类型的包装器;因此“隐藏”):

{-# LANGUAGE GADTs
           , RankNTypes
  #-}

data Some f
  where Some :: f a -> Some f


hide :: (forall a. f a -> g b) -> Some f -> Some g
hide f (Some x) = Some (f x)


data Phantom a = Phantom

cast :: Phantom a -> Phantom b
cast Phantom = Phantom

works :: Some Phantom -> Some Phantom
works = hide cast


doesn't :: Functor f => Some f -> Some f
doesn't = hide (fmap $ \x -> [x])
{-
foo.hs:23:17:
    Couldn't match type ‘b0’ with ‘[a]’
      because type variable ‘a’ would escape its scope
    This (rigid, skolem) type variable is bound by
      a type expected by the context: f a -> f b0
      at foo.hs:23:11-33
    Expected type: f a -> f b0
      Actual type: f a -> f [a]
    In the first argument of ‘hide’, namely ‘(fmap $ \ x -> [x])’
    In the expression: hide (fmap $ \ x -> [x])
    In an equation for ‘doesn't’: doesn't = hide (fmap $ \ x -> [x])
Failed, modules loaded: none.
-}


but :: Functor f => Some f -> Some f
but = hide' (fmap $ \x -> [x])
  where hide' :: (forall a. f a -> g [a]) -> Some f -> Some g
        hide' f (Some x) = Some (f x)

所以我非常理解为什么会这样; works显示hide确实在返回类型与输入类型完全无关时工作,但在doesn't中我使用hide类型的参数调用a -> [a] {1}}。 hide应该“选择”类型aRankNTypes),但b通常是多态的。当b实际上取决于a时,a可能会泄漏。

但是在我实际调用它的上下文中,a实际上并没有泄露出来;我立即将其包裹在Some中。事实上,我可以编写一个备用hide'来接受专门的a -> [a]函数,并使用完全相同的实现工作,只是一个不同类型的签名。

我有什么方法可以输入实现hide f (Some x) = Some (f x)以便它更常用吗?我真的对提升a -> q a类型的函数感兴趣,其中q是一些任意类型的函数;即我希望返回类型取决于a,但我不关心它是如何做到的。可能存在q a是常量的用例(即返回类型不依赖于a),但我认为它们会更加罕见。

这个例子显然非常愚蠢。在我的实际用例中,我有一个GADT Schema a,粗略地说是外部类型系统中的类型; phantom参数提供了一个Haskell类型,可用于表示外部类型系统中的值。我需要使用phantom参数来保证所有类型的安全,但有时我会根据运行时数据构造Schema,在这种情况下我不知道该参数类型是什么。

所以我似乎需要另一种与type参数无关的类型。我希望使用像Some之类的简单存在性包装器来构建Schema,并且能够将forall a. Schema a -> Schema b类型的函数提升为{{}}。 {1}}。因此,如果我遇到XY问题并且我更好地使用其他方法传递Some Schema -> Some Schema来表示未知Schema a,那么这也可以解决我的问题。

2 个答案:

答案 0 :(得分:4)

正如 David Young 所说,你可以写

hide' :: (forall a. f a -> g (q a)) -> Some f -> Some g
hide' f (Some x) = Some (f x)

does :: Functor f => Some f -> Some f
does = hide' (fmap (:[]))

但不是像hide fmap那样,你可以使它像绑定一样:

hide'' :: (forall a. f a -> Some g) -> Some f -> Some g
hide'' f (Some x) = f x

does :: Functor f => Some f -> Some f
does = hide'' (Some . fmap (:[]))

但这有点可以理解。

或者,更一般地说

elim :: (forall a. f a -> c) -> Some f -> c
elim f (Some x) = f x

答案 1 :(得分:2)

我不确定这对您的大型用例有多大用处,因为您必须重构所有现有操作以使用延续传递样式,但是可以使用continuation来实现有效的hide对于这两个示例并保持b完全通用。

hide :: (forall r a. f a -> (forall b. g b -> r g) -> r g) -> Some f -> Some g
hide f (Some x) = f x Some

cast :: Phantom a -> (forall b. Phantom b -> r Phantom) -> r Phantom
cast Phantom f = f Phantom

works :: Some Phantom -> Some Phantom
works = hide cast

alsoWorks :: Functor f => Some f -> Some f
alsoWorks = hide (\a f -> f $ fmap (\x -> [x]) a)

通过分析CPS转换可以让您更轻松地使用原始cast之类的现有功能:

hide :: (forall r a. f a -> (forall b. g b -> r g) -> r g) -> Some f -> Some g
hide f (Some x) = f x Some

cps :: (f a -> g b) -> (f a -> (forall c. g c -> r) -> r)
cps f a c = c (f a)

cast :: Phantom a -> Phantom b
cast Phantom = Phantom

works :: Some Phantom -> Some Phantom
works = hide $ cps cast

alsoWorks :: Functor f => Some f -> Some f
alsoWorks = hide $ cps $ fmap (\x -> [x])