是否可以编写一个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
)但我更感兴趣的是,是否存在一个根本原因,即为什么不允许在存在主义下进行隐藏。
答案 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)
执行该简单程序时会发生这种情况:
等等,直到内存消耗降低了系统。
类型很重要!如果你绕过类型系统,就会规避程序的任何可预测性(即任何事情都可能发生,包括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的答案所示。