Haskell 返回存在类型的函数

Haskell function returning existential type

是否可以编写一个 Haskell 函数来生成隐藏了确切类型参数的参数化类型? IE。类似 f :: T -> (exists a. U a) 的东西?明显的尝试:

{-# LANGUAGE ExistentialQuantification #-}

data D a = D a

data Wrap = forall a. Wrap (D a)

unwrap :: Wrap -> D a
unwrap (Wrap d) = d

编译失败:

Couldn't match type `a1' with `a'
  `a1' is a rigid type variable bound by
       a pattern with constructor
         Wrap :: forall a. D a -> Wrap,
       in an equation for `unwrap'
       at test.hs:8:9
  `a' is a rigid type variable bound by
      the type signature for unwrap :: Wrap -> D a at test.hs:7:11
Expected type: D a
  Actual type: D a1
In the expression: d
In an equation for `unwrap': unwrap (Wrap d) = d

我知道这是一个人为的例子,但我很好奇是否有办法让 GHC 相信我不关心参数化 D 的确切类型,而不引入另一个存在的包装器输入 unwrap.

的结果

澄清一下,我确实想要类型安全,但也希望能够应用一个不关心 a 的函数 dToString :: D a -> String(例如,因为它只是从中提取一个 String 字段D) 到 unwrap 的结果。我意识到还有其他方法可以实现它(例如定义 wrapToString (Wrap d) = dToString d),但我更感兴趣的是是否存在不允许这种隐藏在存在下的根本原因。

是的,您可以让 GHC 相信您不关心 D 参数化的确切类型。只是,这是一个可怕的想法。

{-# LANGUAGE GADTs #-}

import Unsafe.Coerce

data D a = D a deriving (Show)

data Wrap where       -- this GADT is equivalent to your `ExistentialQuantification` version
   Wrap :: D a -> Wrap

unwrap :: Wrap -> D a
unwrap (Wrap (D a)) = D (unsafeCoerce a)

main = print (unwrap (Wrap $ D "bla") :: D Integer)

这是我执行那个简单程序时发生的情况:

依此类推,直到内存消耗导致系统崩溃。

类型很重要! 如果绕过类型系统,就绕过了程序的任何可预测性(即任何事情都可能发生,包括 thermonuclear war or the famous demons flying out of your nose)。


现在,显然您认为类型的工作方式有所不同。在 Python 之类的动态语言中,以及某种程度的 Java 之类的 OO 语言中,类型在某种意义上是 属性值可以有。因此,(参考)值不仅携带区分单一类型不同值所需的信息,还携带区分不同(子)类型的信息。这在很多意义上是相当低效的——这是 Python 如此缓慢而 Java 需要如此巨大的 VM 的主要原因。

在 Haskell 中,类型 在运行时不存在 。一个函数永远不知道它使用的值是什么类型。仅仅因为 编译器 知道它将拥有的所有类型,函数不需要 任何这样的知识——编译器已经硬编码它! (也就是说,除非你用 unsafeCoerce 规避它,正如我所展示的那样,它听起来并不安全。)

如果你确实想将类型作为“属性”附加到一个值,你需要明确地这样做,这就是那些存在的包装器的用途。但是,通常有更好的方法可以用函数式语言来完成。您真正想要的应用程序是什么?


也许回忆一下具有多态结果的签名的含义也很有帮助。 unwrap :: Wrap -> D a 并不意味着“结果是一些 D a... 并且调用者最好不要关心使用的 a”。在 Java 中就是这种情况,但在 Haskell 中就毫无用处,因为对于未知类型的值你无能为力。

相反,它意味着:对于调用者请求的任何 类型 a,此函数能够提供合适的 D a 值。当然,这很难实现——没有额外信息,这就像对给定未知类型的值做任何事情一样不可能。但是如果 参数 中已经有 a 值,或者 a 以某种方式被限制为类型 class(例如 fromInteger :: Num a => Integer -> a,那么这很有可能而且非常有用。


要获得一个String字段——独立于a参数——你可以直接对包装值进行操作:

data D a = D
    { dLabel :: String
    , dValue :: a
    }

data Wrap where Wrap :: D a -> Wrap

labelFromWrap :: Wrap -> String
labelFromWrap (Wrap (D l _)) = l

要更通用地在 Wrap 上编写此类函数(使用任何“不关心 a 的标签访问器”),请使用 n.m 中所示的 Rank2 多态性。的回答。

是的,你可以,但不是直接的方式。

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}

data D a = D a

data Wrap = forall a. Wrap (D a)

unwrap :: Wrap -> forall r. (forall a. D a -> r) -> r
unwrap (Wrap x) k = k x

test :: D a -> IO ()
test (D a) = putStrLn "Got a D something"

main = unwrap (Wrap (D 5)) test

您不能从您的函数中 return a D something_unknown,但您可以提取它并立即将其传递给另一个接受 D a 的函数,如图所示。