Haskell函数返回存在类型

是否有可能编写一个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 (例如,因为它只是从D提取一个字符串字段)的结果unwrap 。 我意识到还有其他的方式来实现它(例如,定义wrapToString (Wrap d) = dToString d ),但我更感兴趣的是,是否存在根本原因,为什么这种隐藏在existential是不允许的。


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

{-# 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 ,但是你可以提取它并且立即将它传递给接受D a另一个函数,如图所示。


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

当我执行这个简单的程序时会发生这种情况:

在这里输入图像描述

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

类型很重要! 如果你规避了类型系统,你可以绕过程序的任何可预测性(即任何事情都可能发生,包括热核战争或着名的恶魔飞出你的鼻子)。


现在,显然你认为类型以某种方式工作不同。 在像Python这样的动态语言中,以及在Java等面向对象语言中的某种程度上,类型在某种意义上是一个值可以具有的属性。 因此,(reference-)值不仅仅包含区分单个类型的不同值所需的信息,还包含区分不同(子)类型的信息。 这在很多方面都非常低效 - 这是Python如此之慢以及Java需要如此庞大虚拟机的主要原因。

在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更一般地(与任何“不关心标签accesor a ”),使用秩2-多态性如图纳米的答案。

链接地址: http://www.djcxy.com/p/43449.html

上一篇: Haskell function returning existential type

下一篇: Why aren't there existentially quantified type variables in GHC Haskell