Haskell类型族和伪参数
我制作了一个类似于numpy的Haskell类型族和伪参数,haskell,type-families,Haskell,Type Families,我制作了一个类似于numpy的数组的函数。它将列表转换为数组,将列表列表转换为二维数组,等等 它的工作原理如下: 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')
数组的函数。它将列表转换为数组,将列表列表转换为二维数组,等等
它的工作原理如下:
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
“应该”是::列表索引i a->(i,i)
。对于acFlatten
,也是如此。每个变量都有一个伪变量(undefined
始终是给定的值),因为否则我无法将其编译:(
上面是在工作时传递的伪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))
acBounds和acFlatten的额外参数是必需的,原因是不能从ListOfIndex i a->(i,i)
和ListOfIndex i a->[a]恢复类型a
和i
分别。一种解决方法是将这两种方法组合成ListOfIndex i a->((i,i),a)
类型的方法acArgs
。现在唯一的问题是在(Int,i)
实例中使用它,以防止typechecker过度泛化其类型导致与以前相同的问题(例如,我们不能简单地使用fst.acArgs
)
{-#语言类型族,FlexibleInstances}
导入数据。数组
索引i a的类型族列表
类型实例ListOfIndex()a=a
类型实例ListOfIndex(Int,i)a=[ListOfIndex i a]
第九类i=>ArrConv i,其中
acArgs::ListOfIndex i a->((i,i),[a])
实例ArrConv(),其中
acArgs x=(((),()),[x])
实例ArrConv i=>ArrConv(Int,i)其中
阿加尔湖=
((0,inStart),(长度lst-1,inEnd)),args>>=snd)
哪里
args=地图acArgs lst
(inStart,inEnd)=fst(头部参数)
arrFromNestedLists::ArrConv i=>ListOfIndex i a->Array i a
arrFromNestedLists=uncurry listArray.acArgs
如果希望将acBounds
和acflatte
分开,可以向其中添加a,即acBounds
将具有类型acBounds::Proxy a->ListOfIndex i a->(i,i)
。这样就不需要未定义的参数,因为您可以只传递(Proxy::SomeConcreteType)
到它;acBounds
无法从中提取任何有用的值级别信息,因为它与单位类型同构(以非类型化的方式)。“我不知道有什么编程方法可以增加元组的长度。”我认为你做不到。这是一个函数类型取决于值的完美例子。用依赖类型的语言(如Agda
)很容易做到,但在Haskell中是不可能的。也许你可以用GADTs
以某种方式给你一些依赖行为,但我不知道怎么做。可能是模板Haskell可能很有用:@primodemus:TH我可以为多达10维的数组创建ArrConv
实例,它们将使用普通元组作为索引,这是一种改进。但我觉得限制是任意的,代码的可读性可能会低得多。看看您的原始示例,TH是如何实现的编译器应该知道您是想要Array(Int,(Int,())Char
还是Array(Int,())String
?我怀疑您的问题固有的模糊性导致需要传递伪参数来修复类型。@Reid Barton:太棒了!谢谢:)我对您的答案进行了一些重构(希望您不介意):它不再将acArgs
传递给函数使其成为单态,而是现在只在一个位置使用它。
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))
{-# 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