我有这种数据类型,应该代表一个表:
data R = R [Bool] deriving Eq -- Row
data T = T [R] deriving Eq -- Table
问题是它允许拥有不同长度的行表,例如:
tab =T [R [True, False, True, True],
R [False, False, True, False],
R [False, False, False, True],
R [False, False]]
是否可以修改T
的数据定义以强制所有R
元素具有相同的长度?
答案 0 :(得分:10)
data BalancedLists_ a as
= Nil [as]
| Cons (BalancedLists_ a (a, as))
type BalancedLists a = BalancedLists_ a ()
例如,包含两个长度为3的列表的平衡列表如下所示:
Cons (Cons (Cons (Nil [(1, (2, (3, ()))), (4, (5, (6, ())))])))
有一篇精彩的论文将此技术扩展到Ralf Hinze称为Manufacturing Datatypes的一百个不同方向。
答案 1 :(得分:8)
您可以使用DataKinds
执行此操作。但这可能过于复杂:
{-# LANGUAGE DataKinds, KindSignatures, GADTs #-}
-- requires 7.4.1, I think
data Nat = S Nat | Z
infixr 0 :.
data R (n :: Nat) where
Nil :: R Z -- like []
(:.) :: Bool -> R n -> R (S n) -- and (:)
data T (n :: Nat) = T [R n]
-- OK
test1 = T [(True :. True :. Nil), (True :. False :. Nil)]
-- will fail
test2 = T [(True :. True :. Nil), (False :. Nil)]
我更推荐使用智能构造函数的@MathematicalOrchids替代方法。
编辑:DataKinds
做了什么。
DataKinds
扩展允许编译器为每个写入的数据类型自动创建除*
以外的新类型,并从构造函数中生成此类型的新类型。
所以Nat
除了是一个简单的ADT之外,还会产生一种Nat
类型构造函数Z :: Nat
和S :: Nat -> Nat
。此S
与Maybe :: * -> *
相当 - 它不会使用所有类型的类型,而是您的新类型Nat
,只能通过自然数字的表示来居住。
关键是,现在您还可以定义混合类型的类型构造函数。典型的例子是Vec
:
data Vec (n :: Nat) (a :: *) where {-...-}
有Vec :: Nat -> * -> *
种。同样,T
也有T :: Nat -> *
种。这样就可以将它与类型编码的常量长度一起使用,如果将两行不同的长度放在一起,则会导致类型错误。
虽然这看起来非常强大,但事实上它受到了限制。要从这些表示中获取所有内容,应使用Agda依赖类型的语言。
答案 2 :(得分:4)
list 类型表示任意大小的容器。您可以使用元组来强制执行特定大小 - 但它只适用于“小”大小。例如:
data R = R (Bool, Bool, Bool, Bool) deriving Eq
现在每行总是包含4个单元格。
如果您真正想要的是强制行可以是任何大小,只要它对于表中的所有行相同 ...要困难得多。有几种方法可以在类型系统中对此进行编码,但它们都不是特别“简单”。
另一种方法是在运行时强制执行该条件,而不是在编译时尝试保证它。您可以编写一个定义行和表类型的模块,但隐藏它们的定义,并且只公开用于处理这些类型的函数,这些函数保留您想要的不变量(即,所有行等长)。
答案 3 :(得分:1)
另一种方法是使用Data.Array
。关于它的一个好处是它允许真正的多维数组而不是数组数组。只需使用元组索引Array
。