nar*_*thi 8 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
Run Code Online (Sandbox Code Playgroud)
无法编译:
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
Run Code Online (Sandbox Code Playgroud)
我知道这是一个人为的例子,但我很好奇是否有办法说服GHC我不关心D参数化的确切类型,而没有为结果引入另一个存在的包装类型unwrap.
为了澄清,我确实希望类型安全,但也希望能够应用一个dToString :: D a -> String不关心的函数a(例如,因为它只是从中提取一个String字段D)到结果unwrap.我意识到还有其他方法可以实现它(例如定义wrapToString (Wrap d) = dToString d),但我更感兴趣的是,是否有一个根本原因是为什么不允许在存在主义下隐藏这种隐藏.
n. *_* m. 12
是的,你可以,但不是直截了当的方式.
{-# 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
Run Code Online (Sandbox Code Playgroud)
您无法D something_unknown从函数中返回a ,但是您可以将其提取并立即将其传递给另一个接受的函数,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)
Run Code Online (Sandbox Code Playgroud)
这是我执行那个简单程序时会发生的事情:
等等,直到内存消耗降低系统.
类型很重要!如果你绕过类型系统,你就会规避程序的任何可预测性(即任何事情都可能发生,包括热核战争或者从鼻子中飞出的着名恶魔).
现在,显然你认为这些类型在某种程度上有所不同.在诸如Python之类的动态语言中,以及在诸如Java之类的OO语言中,类型在某种意义上是值可以具有的属性.因此,(参考)值不仅仅携带区分单一类型的不同值所需的信息,而且还携带用于区分不同(子)类型的信息.这在许多方面都相当低效 - 这是Python速度如此之慢以及Java需要如此巨大的VM的一个主要原因.
在Haskell中,类型在运行时不存在.函数永远不知道它使用的值是什么类型.只是因为编译器知道它将具有的所有类型,该函数不需要任何这样的知识 - 编译器已经对其进行了硬编码!(也就是说,除非你用它来规避它unsafeCoerce,正如我所证明的那样,听起来不安全.)
如果您确实希望将类型作为"属性"附加到值,则需要显式地执行此操作,这就是那些存在包装器的用途.但是,通常有更好的方法在函数式语言中完成它.您想要的应用程序到底是什么?
也许回想一下具有多态结果的签名意味着什么也是有帮助的.unwrap :: Wrap -> D a并不意味着"结果是某些D a......而且来电者最好不关心a使用的".在Java中就是这种情况,但在Haskell中它会毫无用处,因为对于未知类型的值,你无能为力.
相反,它意味着:对于a调用者请求的任何类型,此函数都能够提供合适的D a值.当然,这很难实现 - 没有额外的信息,就像做任何具有未知类型的价值的东西一样不可能.但是如果参数中已经存在a值,或者以某种方式约束到类型类(例如,那么它很可能也非常有用.afromInteger :: 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
Run Code Online (Sandbox Code Playgroud)
要Wrap更一般地编写这样的函数(使用任何"不关心的标签访问者a"),请使用Rank2-polymorphism,如nm的答案所示.