考虑长度索引向量的简单定义:
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进行测试。
如评论者所述,此行为仅在口译时出现。编译时,所有函数都按预期工作。这个问题仍然适用于解释时运行时表示。
答案 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的每次更新或不同的平台而中断。)