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-多态性如图纳米的答案。
上一篇: Haskell function returning existential type
下一篇: Why aren't there existentially quantified type variables in GHC Haskell