具体来说,我想要做的是为每个正整数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的评论。这表明我几乎肯定不需要使用一个(正如我怀疑的那样),我意识到我可以通过一种简单的方式实现我想要的解析器,确实不需要嵌套列表的类型。有用的经验教训。
答案 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)
我不知道你想要对你的解析列表表示什么,所以很难给出一个“足够通用”的答案,所以我会编制一个列表并解析它,你可以告诉它我,如果这是你正在寻找的。 p>
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的方法不同;)