2010-01-11 77 views
5

我做了一个类似于numpy的array的函数。它可以转换列表以阵列,列表,以二维数组列表等Haskell类型族和虚拟参数

它的工作原理是这样的:

ghci> arrFromNestedLists ["hello", "world"] :: Array (Int, (Int,())) Char 
array ((0,(0,())),(1,(4,()))) [((0,(0,())),'h'),((0,(1,())),'e'),((0,(2,())),'l'),((0,(3,())),'l'),((0,(4,())),'o'),((1,(0,())),'w'),((1,(1,())),'o'),((1,(2,())),'r'),((1,(3,())),'l'),((1,(4,())),'d')] 

(Int, (Int,())),而不是(Int, Int),因为我不知道一个程序化的方式来增加的长度一个元组。 (旁边的问题:有没有这样的方式?)

它的编码是尴尬的,我不得不做一个“解决方法”(传递函数的虚拟参数)它的工作。我想知道是否有更好的方法。

因此,这里的代码,与丑恶的解决方法的细节打断:

{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeFamilies #-} 

type family ListOfIndex i a 
type instance ListOfIndex() a = a 
type instance ListOfIndex (Int, i) a = [ListOfIndex i a] 

class Ix i => ArrConv i where 
    acBounds :: a -> ListOfIndex i a -> (i, i) 
    acFlatten :: i -> ListOfIndex i a -> [a] 

acBounds “应该” 是:: ListOfIndex i a -> (i, i)。和acFlatten类似。每个被赋予一个虚拟变量(undefined总是给出的值),否则我无法得到它来编译:(

arrFromNestedLists :: forall i a. ArrConv i => ListOfIndex i a -> Array i a 
arrFromNestedLists lst = 
    listArray 
    (acBounds (undefined :: a) lst) 
    (acFlatten (undefined :: i) lst) 

以上是假undefined参数传递的工作。它告诉GHC其中实例ListOfIndex使用。

instance ArrConv() where 
    acBounds _ = const ((),()) 
    acFlatten _ = (: []) 

下面的功能应该已经在ArrConv一个实例acBounds功能,并且对外宣称只是因为我需要使用ScopedTypeVariables,我不知道我怎么可以在做函数在实例定义中..

acSucBounds 
    :: forall a i. ArrConv i 
    => a -> [ListOfIndex i a] -> ((Int, i), (Int, i)) 
acSucBounds _ lst = 
    ((0, inStart), (length lst - 1, inEnd)) 
    where 
    (inStart, inEnd) = acBounds (undefined :: a) (head lst) 

instance ArrConv i => ArrConv (Int, i) where 
    acBounds = acSucBounds 
    acFlatten _ = concatMap (acFlatten (undefined :: i)) 
+3

“我不知道增加元组长度的编程方式。”我不认为你可以。这是一个功能的完美例子,其类型取决于一个值。使用像'Agda'这样的依赖类型语言很容易,但在Haskell中是不可能的。也许你可以以某种方式使用'GADTs'来给你一些依赖性的行为,但是我不知道如何。 – 2010-01-11 21:02:44

+0

也许模板Haskell可能很有用:http://www.haskell.org/bz/thdoc.htm http://www.haskell.org/haskellwiki/Template_Haskell – primodemus 2010-01-12 00:19:28

+0

@primodemus:有了TH,我可以为'ArrConv'对于多达10个维度的数组,它们将使用正常的元组作为索引,这是一种改进。但是我觉得这个限制是任意的,代码的可读性可能会低得多。 – yairchu 2010-01-12 07:33:31

回答

4

的原因,额外的参数acBounds和acFlatten是必要的是,该类型ai不能从ListOfIndex i a -> (i, i)ListOfIndex i a -> [a]分别回收。一种解决方法是将这两种方法合并为ListOfIndex i a -> ((i, i), a)类型的一种方法acArgs。现在唯一的问题是在(Int, i)的情况下使用它,以防止类型检查者将其类型概括得太多,导致与以前相同的问题(例如,我们不能简单地使用fst . acArgs)。

 
{-# LANGUAGE TypeFamilies, FlexibleInstances #-} 

import Data.Array 

type family ListOfIndex i a 
type instance ListOfIndex() a = a 
type instance ListOfIndex (Int, i) a = [ListOfIndex i a] 

class Ix i => ArrConv i where 
    acArgs :: ListOfIndex i a -> ((i, i), [a]) 

instance ArrConv() where 
    acArgs x = (((),()), [x]) 

instance ArrConv i => ArrConv (Int, i) where 
    acArgs lst = 
    (((0, inStart), (length lst - 1, inEnd)), args >>= snd) 
    where 
     args = map acArgs lst 
     (inStart, inEnd) = fst (head args) 

arrFromNestedLists :: ArrConv i => ListOfIndex i a -> Array i a 
arrFromNestedLists = uncurry listArray . acArgs 
+0

@Reid Barton:真棒!谢谢:)我重构了你的答案一点(我希望你不介意):而不是传递'acArgs'到一个函数使它成为单形,它现在只在一个地方使用它。 – yairchu 2010-01-12 16:16:27

+0

啊,我明白了。尼斯。 – 2010-01-12 16:25:47

0

如果你想保持acBoundsacFlatten独立的,你可以一个type-level tag argument添加到它,即acBounds将有类型acBounds :: Proxy a -> ListOfIndex i a -> (i, i)。这消除了undefined参数的需要,因为您只需将(Proxy :: SomeConcreteType)传递给它;和acBounds没有办法从中提取任何有用的值级信息,因为它与单元类型是同构的(以无类型的方式)。