在Haskell中模拟“静态依赖类型”(如果需要,使用GHC扩展)

时间:2011-03-25 08:05:07

标签: haskell types

假设我有一个类型类Vec,它实现了理论上的向量空间理论。

class Vec a where
    (+)  :: a -> a -> a
    zero :: a
    -- rest omitted

现在给出一个自然数n,我可以很容易地构造一个Vec的实例,其基础类型是有理数列表的类型,它实现了维数为n的向量空间。我们在下面采用n = 3:

newtype RatList3 = RatList3 { list3 :: [Rational] }
instance Vec RatList3 where
    v + w = RatList3 (zipWith (Prelude.+) (list3 v) (list3 w))
    zero  = RatList3 (take 3 (repeat 0))

对于另一个自然数,例如计算出的数,我可以写

f :: Int -> Int
f x = x * x -- some complicated function
n :: Int
n = f 2
newtype RatListN = RatListN { listN :: [Rational] }
instance Vec RatListN where
    v + w = RatListN (zipWith (Prelude.+) (listN v) (listN w))
    zero  = RatListN (take n (repeat 0))

现在我有两种类型,一种用于维度3的向量空间,另一种用于维度为n的向量空间。但是,如果我想将我的instance Vec RatList?形式的实例声明放在我不知道主程序最终使用的模块中,我有一个问题,因为类型RatList?没有知道它属于哪个。

为解决这个问题,我尝试按以下方式做一些事情:

class HasDim a where
    dim      :: Int

instance (HasDim a, Fractional a) => Vec [a] where
    v + w = ...
    zero  = take dim (repeat (fromRational 0))

-- in the main module
instance HasDim Rational where
    dim = n -- some integer

当然,这不起作用,因为dim中的HasDim独立于类型变量a,而instance (HasDim a) => Vec [a]中不清楚哪个类型dim 1}}采取。我试图通过引入另一种类型来解决第一个问题:

newtype Dim a = Dim { idim :: Int }

然后我可以写

class HasDim a where
    dim      :: Dim a

但是,我不清楚如何在instance (HasDim a) => Vec [a] where中使用它。此外,我的整个“解决方案”看起来相当麻烦,而提出的问题看起来很简单。 (我认为用C ++模板编写代码很容易。)

修改

我除了ephemient的答案,因为没有类型算术它以我想要的方式解决了我的问题。仅供参考,我的最终解决方案如下:

class Vec a where
    zero :: a
    -- ...

n :: Int
n = 10

newtype RatListN = RatListN [Rational]

instance Vec RatListN where
    zero = RatListN . take n $ repeat 0
    -- ...

1 个答案:

答案 0 :(得分:2)

这似乎是type arithmetic可以为您提供所需内容的情况。

data Zero
data Succ a
type One = Succ Zero
type Two = Succ One
type Three = Succ Two
-- ...

class NumericType a where
    toNum :: (Num b) => a -> b
instance NumericType Zero where
    toNum = const 0
instance (NumericType a) => NumericType (Succ a) where
    toNum (Succ a) = toNum a + 1

data RatList a b = RatList { list :: [b] }
instance (NumericType a, Num b) => Vec (RatList a b) where
    zero = RatList . take (toNum (undefined :: a)) $ repeat 0

现在RatList Two IntRatList Three Int是不同的类型。另一方面,这确实会阻止您在运行时实例化新的维度...