Haskell函数返回存在类型

时间:2015-10-11 10:15:53

标签: haskell existential-type

是否可以编写一个Haskell函数,该函数产生一个参数化类型,其中隐藏了确切的类型参数?即像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的结果。

为了澄清,我确实希望类型安全,但也希望能够应用不关心dToString :: D a -> String的函数a(例如,因为它只是从{{提取字符串字段) 1}})到D的结果。我意识到还有其他方法可以实现它(例如定义unwrap)但我更感兴趣的是,是否存在一个根本原因,即为什么不允许在存在主义下进行隐藏。

2 个答案:

答案 0 :(得分:12)

是的,你可以,但不能直截了当。

pair.second

您无法从函数中返回{-# 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 ,但您可以将其解压缩并立即将其传递给另一个接受D something_unknown的函数,如图所示。

答案 1 :(得分:7)

是的,您可以说服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)

执行该简单程序时会发生这种情况:

enter image description here

等等,直到内存消耗降低了系统。

类型很重要!如果你绕过类型系统,就会规避程序的任何可预测性(即任何事情都可能发生,包括thermonuclear war或着名的demons flying out of your nose)。

现在,显然你认为这些类型在某种程度上有所不同。在诸如Python之类的动态语言中,以及在诸如Java之类的OO语言中,类型在某种意义上是值可以具有的属性。因此,(reference-)值不仅仅包含区分单个类型的不同值所需的信息,还包括区分不同(子)类型的信息。这在许多方面都是相当低效的 - 这是Python速度如此之慢以及Java需要如此巨大的VM的一个主要原因。

在Haskell中,类型在运行时不存在。函数永远不知道它使用的值是什么类型。只是因为编译器知道它将具有的所有类型,该函数不需要任何此类知识 - 编译器已经对其进行了硬编码! (也就是说,除非你用unsafeCoerce来规避它,正如我所说的那样,听起来不安全。)

如果您确实希望将类型作为“属性”附加到值,则需要显式地执行此操作,这就是那些存在性包装器的用途。但是,通常有更好的方法在函数式语言中完成它。您想要的应用程序到底是什么?

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

相反,它意味着:对于调用者请求的任何类型a,此函数能够提供合适的D a值。当然,这很难实现 - 没有额外的信息,就像做任何具有未知类型的价值的东西一样不可能。但是如果参数中已有a个值,或者a以某种方式约束到类型类(例如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的“标签访问者”),使用Rank2-polymorphism,如nm的答案所示。