如何在Haskell中递归定义一系列类型?

时间:2014-06-25 16:40:45

标签: haskell

具体来说,我想要做的是为每个正整数n定义级别为n的列表的类型。我的意思是看起来有点像这样(但可能不完全是)。首先,对于任何类型a,让List a[a]。 (我知道该怎么做。)现在我想

IteratedList 0 a = a

IteratedList n a = List (IteratedList (n-1) a)

因此,例如,IteratedList 3 a将为[[[a]]]

我想要这个的原因是我想编写一个解析器,它可以通过识别开始括号,挑出一堆级别为n-1的列表,然后识别结束括号来挑选级别n的列表。但我不知道如何为解析器做出适当的类型声明。

也许我以错误的方式处理事情。如果是这样,那么正确的方法是什么?

稍后添加。非常感谢下面的有用答案。最后,由(i)难以使用嵌套列表概念和(ii)n.m的评论。这表明我几乎肯定不需要使用一个(正如我怀疑的那样),我意识到我可以通过一种简单的方式实现我想要的解析器,确实不需要嵌套列表的类型。有用的经验教训。

3 个答案:

答案 0 :(得分:5)

这很棘手,但可以使用某种类型级别的黑客攻击。可悲的是,它比你想要的更复杂。

我避免使用封闭类型系列,因此将使用GHC 7.6及更高版本进行编译

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}

import Data.Proxy

data Nat = S Nat | Z -- Type level natural numbers, Z means zero, S n means n + 1
type family IteratedList (n :: Nat) a
type instance IteratedList Z a     = [a]                -- Base case
type instance IteratedList (S n) a = [IteratedList n a] -- Inductive step

请注意,这与数学归纳完全一样吗?我们在IteratedList n时“证明”了Z是什么,然后我们可以使用{{1} n S n IteratedList n a来“证明”它是什么}。

这实际上可以达到相当深的水平,我们很快就会开始冲击贫穷的GHC开放式家庭的界限,最终需要GHC 7.8。

为了帮助GHC的类型检查器完成所有这些,我们需要提供所谓的Proxy s。这些只是带有幻像类型变量的简单数据类型,我们用它来表示类型检查器的有用内容。

这是我们稍后使用的辅助函数,它接受一个大于零的数字的Proxy,并返回一个数字减1的代理。

predProxy :: Proxy (S n) -> Proxy n
predProxy = reproxy

现在我们使用类型类来概括操作 IteratedList秒。这些类基本上像归纳一样工作。 例如,这是在IteratedList内创建单个元素的类。

class Single (n :: Nat) a where
  single :: Proxy n -> a -> IteratedList n a
instance Single Z a where
  single _ = (:[])
instance Single n a => Single (S n) a where
  single p a = [single (predProxy p) a]

需要Proxy n参数来帮助GHC的类型检查器了解正在发生的事情,否则它将生成许多新的类型变量,然后固执地不统一它们。据推测,有一个聪明的理由。

请注意,类型类的两个实例看起来与我们定义IteratedList的方式非常相似?一个用于Z,然后是归纳步骤。

至于在运行时构造它...从技术上讲,这只是基于类型同义词,所以你实际上并不需要转换例程,只是为了构建列表。棘手的一点是,您需要在存在方框中n或在代码中包含CPS,以便您可以拥有1种返回类型。

否则你的回报类型将取决于你的输入,你无法表达对GHC如此复杂的东西。

你可以想象像

这样的东西
buildIteratedList :: String -> (forall n. IteratedList n a -> r) -> r

但是这很快就变得非常可怕,因为每个操作都需要是一个类型感应goop才能在n上通用。

我建议你做一些简单的事情,比如

data Nested a = Elem a
              | List [Nested a]

并进行运行时检查..如果没有依赖类型,这对我来说似乎是不可行的。

答案 1 :(得分:2)

我不会完全推荐这个,但你可以使用GHC 7.8中新的类型文字Nat机制来实现这一点。

{-# LANGUAGE KindSignatures, TypeFamilies #-}

import GHC.TypeLits

type family IteratedList (n :: Nat) a where
    IteratedList 0 a = [a]
    IteratedList n a = IteratedList (n - 1) [a]

这使用封闭类型族来递归Nat的结构,每次递归时都添加[]的类型级别层。

答案 2 :(得分:0)

我不知道你想要对你的解析列表表示什么,所以很难给出一个“足够通用”的答案,所以我会编制一个列表并解析它,你可以告诉它我,如果这是你正在寻找的。

import Data.List.Split
import Data.List(span)

data NestedList a = NestedList{ nesting :: Int, elems :: [a] } 
  deriving (Show, Eq)

testlist = "[ [ [1,2], [3,4], [5,6], [1] ], [ [9] ] ]"

parseList :: Read a => String -> NestedList [a]
parseList = merge . go 0
    where go n []       = []
          go n (x:xs) = case x of
            '[' -> go (n + 1) xs
            ']' -> go (n - 1) xs
            ',' -> go n xs
            ' ' -> go n xs
            _   ->  NestedList n (map read $ splitOn "," elems) : go n rest
                      where (elems, rest) = span (`notElem` "[]") (x:xs)
          merge [] = NestedList 0 []
          merge (NestedList n xs:ns) = 
            let next = elems (merge ns)
            in NestedList n (xs:next)

这绝不是完美的,特别是因为三重嵌套9被解释为“只是另一个元素”,但它是一个开始。

*> parseList testlist :: NestedList [Int]
*> NestedList {nesting = 3, elems = [[1,2],[3,4],[5,6],[1],[9]]}

这个想法基本上是为了跟踪解析树时的深度,在过程中构建一堆嵌套列表,然后将它们集成到一个大的嵌套列表中。同样,这不是完美的,但它是一个开始(和尝试将依赖类型推入Haskell的方法不同;)