是否可以在Haskell中定义一个返回n维列表的函数

是否可以在Haskell中定义一个返回n维列表的函数,haskell,Haskell,我想做一些事情,比如: makeList n | n == 0 = [0 ..] | n > 0 = repeat $ makeList (n - 1) | n < 0 = undefined oneD = [0 ..] twoD = map (\x -> map (\y -> (x, y)) [0 ..]) [0..] threeD = .. -- hard to code 有没有一种优雅的方法来构建高维列表?makeList的最大问题是

我想做一些事情,比如:

makeList n
    | n == 0 = [0 ..]
    | n > 0  = repeat $ makeList (n - 1)
    | n < 0  = undefined
oneD = [0 ..]
twoD = map (\x -> map (\y -> (x, y)) [0 ..]) [0..]
threeD = .. -- hard to code

有没有一种优雅的方法来构建高维列表?

makeList的最大问题是您无法轻松为其指定类型。您可以做的一件事是使用递归类型而不是列表的列表。您没有一系列的类型,
[a]
[[a]]
[[a]]]
,…,而是使用一个类型
重新列出一个
,它表示具有任意级别数目的嵌套列表

下面是一个使用长度为1的列表而不是无限列表的简化示例:

data RecList a
  = Elem a
  | Dim [RecList a]
  deriving (Eq, Show)

makeNested :: Int -> RecList String
makeNested n
    | n == 0 = Elem "Hi There"
    | n > 0  = Dim [(makeList (n - 1))]
    | n < 0  = undefined

通过使用这样的递归类型,可以使维度的数量成为值级别的东西,而不是类型级别的东西。它没有基于类型级别或元编程的解决方案的静态安全性,但更易于实现。

您可以通过向
RecList
添加类型索引来改进@hugomg答案中的代码,该索引跟踪
RecList
中的维度数:

{-# LANGUAGE GADTs, DataKinds, KindSignatures, TypeFamilies, StandaloneDeriving #-}
import Data.Singletons
import Data.Singletons.TH

data Nat = Z | S Nat

data RecList (d :: Nat) a where
    Elem :: a -> RecList Z a
    Dim :: [RecList d a] -> RecList (S d) a
deriving instance (Show a) => Show (RecList d a)
这样,您将知道
RecList d a
具有确切的
d
维度

下面是一个使用库将类型级别索引具体化为一个术语的示例,因此我们可以在其上递归,以生成一个无限的
()
s的
-dimensional网格:

这里有一些更接近你的例子:

makeList :: Sing d -> RecList (S d) Int
makeList SZ = Dim $ map Elem [0..]
makeList (SS d) = Dim $ repeat $ makeList d

在hugomg的回答中,我们有

data RecList a
  = Elem a
  | Dim [RecList a]
  deriving (Eq, Show)
对于某些n,这些并不完全代表n维列表;事实上,它们允许混合不同维度的列表,如
Dim[Elem 0,Dim[Elem 0]]
。在Cactus的回答中,我们有一个更复杂的类型,它通过在类型级别公开维度并强制所有列表都具有该维度来解决这个问题;但它使用了一些相当复杂的扩展

在这个答案中,我们将给出一个不由混合维度列表占据的类型,但它不需要扩展。其思想就是通过向更高维度的列表提供更多的构造函数来跟踪维度。因此:

data DeepList a = Z a | S (DeepList [a]) deriving Show
每个值都是一个给出维度的Peano nat,后面是该维度的列表。因此:

Z 0
S (Z [0])
S (S (Z [[0]]))
都是价值观。另一方面,我们根本不能写这样的东西

S (S (Z ["zero", ["zero"]]))
因为它打字不好

我们可以构造这种类型的值。出于测试的目的,我将使用
replicate 3
而不是
repeat
,但是想法是一样的

makeList :: Int -> a -> DeepList a
makeList 0 v = S (Z (replicate 3 v))
makeList n v = S (makeList (n-1) (replicate 3 v))
在ghci中:

*Main> makeList 2 5
S (S (S (Z [[[5,5,5],[5,5,5],[5,5,5]],[[5,5,5],[5,5,5],[5,5,5]],[[5,5,5],[5,5,5],[5,5,5]]])))
*Main> putStrLn . take 100 . show $ nD 3
S (S (S (Z [[[[0,0,0],[1,0,0],[2,0,0],[3,0,0],[4,0,0],[5,0,0],[6,0,0],[7,0,0],[8,0,0],[9,0,0],[10,0,
*Main> let S (S (S (Z v))) = nD 3
*Main> putStrLn . take 100 . show . drop 1 $ v
[[[[0,0,1],[1,0,1],[2,0,1],[3,0,1],[4,0,1],[5,0,1],[6,0,1],[7,0,1],[8,0,1],[9,0,1],[10,0,1],[11,0,1]
*Main> putStrLn . take 100 . show . map (drop 1) $ v
[[[[0,1,0],[1,1,0],[2,1,0],[3,1,0],[4,1,0],[5,1,0],[6,1,0],[7,1,0],[8,1,0],[9,1,0],[10,1,0],[11,1,0]
*Main> putStrLn . take 100 . show . map (map (drop 1)) $ v
[[[[1,0,0],[2,0,0],[3,0,0],[4,0,0],[5,0,0],[6,0,0],[7,0,0],[8,0,0],[9,0,0],[10,0,0],[11,0,0],[12,0,0
一旦我们为
深度列表
提供了一个
函子
实例,编写索引列表也不难:

instance Functor DeepList where
    fmap f (Z v) = Z (f v)
    fmap f (S v) = S (fmap (map f) v)

nD :: Int -> DeepList [Int]
nD 0 = Z []
nD n = S (fmap (\v -> map (:v) [0..]) (nD (n-1)))
在ghci中:

*Main> makeList 2 5
S (S (S (Z [[[5,5,5],[5,5,5],[5,5,5]],[[5,5,5],[5,5,5],[5,5,5]],[[5,5,5],[5,5,5],[5,5,5]]])))
*Main> putStrLn . take 100 . show $ nD 3
S (S (S (Z [[[[0,0,0],[1,0,0],[2,0,0],[3,0,0],[4,0,0],[5,0,0],[6,0,0],[7,0,0],[8,0,0],[9,0,0],[10,0,
*Main> let S (S (S (Z v))) = nD 3
*Main> putStrLn . take 100 . show . drop 1 $ v
[[[[0,0,1],[1,0,1],[2,0,1],[3,0,1],[4,0,1],[5,0,1],[6,0,1],[7,0,1],[8,0,1],[9,0,1],[10,0,1],[11,0,1]
*Main> putStrLn . take 100 . show . map (drop 1) $ v
[[[[0,1,0],[1,1,0],[2,1,0],[3,1,0],[4,1,0],[5,1,0],[6,1,0],[7,1,0],[8,1,0],[9,1,0],[10,1,0],[11,1,0]
*Main> putStrLn . take 100 . show . map (map (drop 1)) $ v
[[[[1,0,0],[2,0,0],[3,0,0],[4,0,0],[5,0,0],[6,0,0],[7,0,0],[8,0,0],[9,0,0],[10,0,0],[11,0,0],[12,0,0

这是两个问题合一。。。第二种方法有一些简单的解决方案,我相信你会觉得很有启发性,但是第一种方法,如果可以解决的话,很可能会涉及一些大型的基于单例的框架和大量的类型级编程。你确定不想把你的问题一分为二吗?@Cactus我对
类型级别的编程非常感兴趣,你能帮我把问题一分为二吗,因为我是haskell的新手,无法分辨这两个问题之间的区别?很有启发性,但Daniel Wagner的答案看起来更合适,所以我改变了接受。谢谢你的回答。非常好,让我看起来像个白痴,因为我带着枪参加了一场刀战:)奥托,我的可以通过存在主义转化为这样的东西,而这不能转化为静态地知道它的维度数的东西。。。所以它不是那么明确。@Cactus事实上,这三个答案只是方便性和强静态保证之间权衡空间的三点。哪一个是“正确的”在很大程度上取决于给定应用程序需要什么。一个问题是,如果我删除makeList的类型声明,
makeList::Int->a->DeepList a
,GHC会抱怨一个错误。为什么?这是因为如果没有类型签名,类型推理会将一个单态类型分配给同一个相互递归的定义组中的定义,然后它才会被泛化为多态类型。