[]类型的特殊运行时表示?

时间:2016-03-17 07:31:47

标签: haskell ghc

考虑长度索引向量的简单定义:

data Nat = Z | S Nat 

infixr 5 :> 
data Vec (n :: Nat) a where 
  V0 :: Vec 'Z a 
  (:>) :: a -> Vec n a -> Vec ('S n) a 

当然,我在某些时候需要以下功能:

vec2list :: Vec n a -> [a]  

然而,这个功能实际上只是一个奇特的身份。我相信这两种类型的运行时表示是相同的,所以

vec2list :: Vec n a -> [a]  
vec2list = unsafeCoerce 

应该有效。唉,它没有:

>vec2list ('a' :> 'b' :> 'c' :> V0)
""

每个输入只返回空列表。所以我认为我的理解是缺乏的。为了测试它,我定义了以下内容:

data List a = Nil | Cons a (List a) deriving (Show) 

vec2list' :: Vec n a -> List a 
vec2list' = unsafeCoerce 

test1 = vec2list' ('a' :> 'b' :> 'c' :> V0)

data SomeVec a = forall n . SomeVec (Vec n a) 

list'2vec :: List a -> SomeVec a 
list'2vec x = SomeVec (unsafeCoerce x) 

令人惊讶的是这有效!这当然不是GADT的问题(我最初的想法)。

我认为List类型在运行时与[]完全相同。我也试着测试一下:

list2list :: [a] -> List a 
list2list = unsafeCoerce 

test2 = list2list "abc" 

它有效!基于这一事实,我必须得出结论:[a]List a必须具有相同的运行时表示。然而,以下

list2list' :: List a -> [a] 
list2list' = unsafeCoerce 

test3 = list2list' (Cons 'a' (Cons 'b' (Cons 'c' Nil)))

不起作用。 list2list'再次始终返回空列表。我相信“具有相同的运行时表示”必须是对称关系,所以这似乎没有意义。

我开始认为也许有一些有趣的“原始”类型 - 但我始终认为[]只是特殊的语法,而不是语义。似乎就是这样:

data Pair a b = Pair a b deriving (Show, Eq, Ord)  

tup2pair :: (a,b) -> Pair a b 
tup2pair = unsafeCoerce 

pair2tup :: Pair a b -> (a,b) 
pair2tup = unsafeCoerce 

第一个函数有效,第二个函数不起作用 - 与List[]的情况相同。虽然在这种情况下,pair2tup段错误而不是始终返回空列表。

对于使用“内置”语法的类型,似乎始终不对称。返回Vec示例,以下

list2vec :: [a] -> SomeVec a 
list2vec x = SomeVec (unsafeCoerce x) 

效果也不错! GADT真的不特别。

问题是:使用“内置”语法的类型的运行时表示与不支持的类型的运行时表示有何不同?

或者,如何编写从Vec n a[a]的零成本强制?这不回答问题,但解决了问题。

使用GHC 7.10.3进行测试。

如评论者所述,此行为仅在口译时出现。编译时,所有函数都按预期工作。这个问题仍然适用于解释时运行时表示。

2 个答案:

答案 0 :(得分:9)

现在回答您的主要问题,this thread appears to have the answer:使用-fobject-code启动ghci:

$ ghci /tmp/vec.hs
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( /tmp/vec.hs, interpreted )
Ok, modules loaded: Main.
*Main> print $ vec2list ('a' :> 'b' :> 'c' :> V0)
""

使用-fobject-code

$ ghci -fobject-code /tmp/vec.hs
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( /tmp/vec.hs, /tmp/vec.o )
Ok, modules loaded: Main.
Prelude Main> print $ vec2list ('a' :> 'b' :> 'c' :> V0)
"abc"

包含[](,)的模块都已编译,这导致其运行时表示与解释模块中的同构数据类型不同。根据我链接的线程上的Simon Marlow,解释模块为调试器添加注释。我认为这也解释了为什么tup2pair有效且pair2tup没有解释:缺少注释并不是解释模块的问题,但编译的模块会扼杀额外的注释。

-fobject-code有一些缺点:编译时间较长,只会将导出的函数放在范围内,但它还有一个额外的优点,即运行代码要快得多。

答案 1 :(得分:2)

要仅回答您的替代问题,您可以使用未导出的构造函数创建newtype,以便为列表提供类型级别长度和零成本强制列表:

{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}

module Vec (Nat(..), Vec, v0, (>:>), vec2list) where

data Nat = Z | S Nat

newtype Vec (n :: Nat) a = Vec { unVec :: [a] }

v0 :: Vec Z a
v0 = Vec []

infixr 5 >:>
(>:>) :: a -> Vec n a -> Vec ('S n) a
a >:> (Vec as) = Vec (a : as)

vec2list :: Vec n a -> [a]
vec2list (Vec as) = as

只要Vec构造函数不在范围内(因此只有v0>:>可用于构造向量),类型级数表示长度的不变量可以不被侵犯。

(这种方法绝对优先于unsafeCoerce,因为unsafeCoerce的任何内容都可能会因GHC的每次更新或不同的平台而中断。)