是否可以在Haskell中递归地定义列表?

时间:2013-09-10 23:17:55

标签: haskell

在几种编程语言(包括JavaScript,Python和Ruby)中,可以在其自身内部放置一个列表,这在使用列表表示无限详细的分形时非常有用。但是,我尝试在Haskell中执行此操作,并且它没有像我预期的那样工作:

--aList!!0!!0!!1 should be 1, since aList is recursively defined: the first element of aList is aList.
main = putStrLn $ show $ aList!!0!!0!!1

aList = [aList, 1]

该程序产生了这个编译器错误,而不是打印1

[1 of 1] Compiling Main             ( prog.hs, prog.o )

prog.hs:3:12:
    Occurs check: cannot construct the infinite type: t0 = [t0]
    In the expression: aList
    In the expression: [aList, 1]
    In an equation for `aList': aList = [aList, 1]

是否有可能在Haskell中放入一个列表,因为我试图在这里做?

5 个答案:

答案 0 :(得分:17)

不,你不能。首先,有一个轻微的术语混淆:你所拥有的是列表,而不是数组(which Haskell also has),尽管这一点是两种方式。那么,就像Haskell一样,你必须问自己: aList = [aList, 1]的类型是什么

让我们考虑更简单的aList = [aList]案例。我们知道aList必须是某些内容的列表,因此某些类型aList :: [α]需要α。什么是α?作为列表元素的类型,我们知道α必须是aList的类型;也就是α ~ [α],其中~表示类型相等。所以α ~ [α] ~ [[α]] ~ [[[α]]] ~ ⋯ ~ [⋯[α]⋯] ~ ⋯。这确实是一种无限类型,Haskell禁止这样的事情。

如果值为aList = [aList, 1],您也有1 :: α的限制,但所有可以让我们得出结论的是,必须有Num α约束({{1} }}),它不会改变任何东西。


显而易见的接下来的三个问题是:

  1. 为什么Haskell列表只包含一种元素?
  2. 为什么Haskell禁止无限类型?
  3. 我该怎么办?
  4. 让我们按顺序解决这些问题。


    第一:为什么Haskell列表只包含一种元素?这是因为Haskell的类型系统。假设您有一个不同类型的值列表:Num α => [⋯[α]⋯]。函数[False,1,2.0,'c']的类型是什么?没有一个,因为你不知道你会得到什么类型。那么你还能用这个价值做什么呢?毕竟,你对此一无所知!


    第二:为什么Haskell禁止无限类型?无限类型的问题在于它们不会添加许多功能(您可以始终将它们包装在新类型中;请参见下文),并且它们会进行一些真正的错误类型检查。例如,in the question "Why does this Haskell code produce the ‘infinite type’ error?",无限类型的不存在排除了someElement n = [False,1,2.0,'c'] !! n的错误实现(甚至没有明确的类型签名)。


    第三:我能做些什么?如果要在Haskell中伪造无限类型,则必须使用递归数据类型。数据类型阻止类型具有真正的无限扩展,并且显式性避免了上面提到的意外错误。因此,我们可以为无限嵌套列表定义一个新类型,如下所示:

    intersperse

    这让我们得到了我们想要的无限嵌套列表 - 打印它永远不会终止 - 但类型都不是无限的。 (Prelude> newtype INL a = MkINL [INL a] deriving Show Prelude> let aList = MkINL [aList] Prelude> :t aList aList :: INL a Prelude> aList MkINL [MkINL [MkINL [MkINL ^CInterrupted. isomorphic INL a,但它与相等。如果你对此感到好奇,那么差异在{{ 3}})

    但请注意,这种类型不是很有用;它包含的唯一列表是无限嵌套的东西,如[INL a],或者是空列表的各种嵌套集合。没有办法将aList类型的基本案例放入其中一个列表中:

    a

    所以你想要的列表是任意嵌套列表。 isorecursive types (what Haskell has) and equirecursive types (which allow infinite types).对这些问题有疑问,需要定义新的数据类型:

    Prelude> MkINL [()]
    
    <interactive>:15:8:
        Couldn't match expected type `INL a0' with actual type `()'
        In the expression: ()
        In the first argument of `MkINL', namely `[()]'
        In the expression: MkINL [()]
    

    data NestedList a = Elem a | List [NestedList a] 的每个元素都是NestedList a类型的普通值,或者是更多a的列表。 (这与任意分支树一样,只在其叶子中存储数据。)然后你有

    NestedList a

    您现在必须定义自己的查找函数,并注意它可能具有类型Prelude> data NestedList a = Elem a | List [NestedList a] deriving Show Prelude> let aList = List [aList, Elem 1] Prelude> :t aList aList :: NestedList Integer Prelude> aList List [List [List [List ^CInterrupted. - NestedList a -> Int -> Maybe (NestedList a)用于处理超出范围的整数,但重要的部分是它不能只返回Maybe。毕竟,a不是整数!

答案 1 :(得分:8)

是。如果您想要一个包含自身的值,您将需要一个包含自身的类型。这没问题;例如,您可能喜欢玫瑰树,在Data.Tree中大致定义如下:

data Tree a = Node a [Tree a]

现在我们可以写:

recursiveTree = Node 1 [recursiveTree]

答案 2 :(得分:6)

对于Haskell中的列表类型,这是不可能的,因为每个元素必须是相同的类型,但是您可以创建一个数据类型来执行它。不过,我不确定你为什么要这么做。

data Nested a
    = Value a
    | List [Nested a]
    deriving (Eq, Show)

nested :: Nested Int
nested = List [nested, Value 1]

(!) :: Nested a -> Int -> Nested a
(!) (Value _) _ = undefined
(!) (List xs) n = xs !! n

main = print $ nested ! 0 ! 0 ! 1

这将打印出Value 1,这种结构可能会有所帮助,但我认为它非常有限。

答案 3 :(得分:1)

有几个答案从“是的你可以”到“不,你绝对不能”。嗯,两者都是对的,因为它们都涉及你问题的不同方面。

另一种向自身添加“数组”的方法是允许列出任何内容。

{-# LANGUAGE ExistentialQuantification #-}

data T = forall a. T a
arr :: [T]
arr = [T arr, T 1]

所以,这会给自己添加arr,但你不能用它做任何其他事情,除了证明它是一个有效的构造并编译它。

由于Haskell是强类型的,访问列表元素会给你T,你可以提取包含的值。但那个价值的类型是什么?它是“forall a.a” - 可以是任何类型,实质上意味着根本没有任何函数可以对它做任何事情,甚至不打印,因为这需要一个可以将任何类型转换为String的函数。请注意,这不是Haskell特有的 - 即使在动态语言中也存在问题;没有办法弄清楚arr !! 1的类型,你只假设它是一个Int。 Haskell与其他语言的不同之处在于它不会让你使用该函数,除非你能解释表达式的类型。

这里的其他例子定义了归纳类型,这并不是你所要求的,但是它们表现出易于处理的自我引用。


以下是你如何能够做出明智的结构:

{-# LANGUAGE ExistentialQuantification #-}

data T = forall a. Show a => T a

instance Show T where -- this also makes Show [T],
        -- because Show a => Show [a] is defined in standard library
  show (T x) = show x

arr :: [T]
arr = [T arr, T 1]

main = print $ arr !! 1

现在由T包装的内部值被限制为Show的任何实例(“OOP用语中的”Show interface“的实现”),因此您至少可以打印列表的内容。

请注意,之前我们不能仅仅因为a和[a]之间没有共同点而包含arr。但是,一旦您可以确定列表中所有元素支持的常见操作,后一个示例就是一个有效的构造。如果你可以为[T]定义这样的函数,那么你可以在自己的列表中包含arr - 这个函数确定某些类型的a和[a]之间的共同点。

答案 4 :(得分:0)

没有。我们可以效仿:

data ValueRef a = Ref | Value a deriving Show

lref :: [ValueRef Int]
lref = [Value 2, Ref, Value 1]

getValue :: [ValueRef a] -> Int -> [ValueRef a]
getValue lref index = case lref !! index of
     Ref -> lref
     a -> [a]

并有结果:

>getValue lref 0
[Value 2]
>getValue lref 1
[Value 2,Ref,Value 1]

当然,我们可以重复Maybe a而不是ValueRef a