考虑如下的递归数据结构:
data Tree level
= Leaf String
| Node level [ Tree level ]
现在,如果level
是Ord
的实例,我想在类型级别对数据结构施加以下限制:一个节点必须只包含Tree
个更高的level
。
您可以放心地假设level
是一个简单的和类型,如
Level
= Level1
| Level2
...
| LevelN
但N
不知道先验。在这种情况下,我可以让节点的所有子节点都具有更高的级别。
例如
tree = Node Level1
[ Node Level2 []
, Node Level3 []
]
应编译,而
tree = Node Level2
[ Node Level1 []
]
不应该。
是否有可能在Haskell中建模这样的东西?
答案 0 :(得分:10)
这是基本想法。像这样编码递归限制的最简单方法是使用Peano numbers。让我们定义这样一种类型。
data Number = Zero | Succ Number
数字为零或另一个数字的后继。这是一个在这里定义数字的好方法,因为它将与我们的树递归很好地相处。现在,我们希望Level
是一个类型,而不是一个值。如果它是一个值,我们不能在类型级别限制它的值。所以我们使用GADT来限制我们初始化事物的方式。
data Tree (lvl :: Number) where
Leaf :: String -> Tree lvl
Node :: [Tree lvl] -> Tree ('Succ lvl)
lvl
是深度。 Leaf
节点可以具有任何深度,但Node
节点的深度受到限制,并且必须严格大于其子节点(这里,严格地说是一个更大的节点,在大多数简单情况下都适用。一般来说它要严格得多,需要一些更复杂的类型级技巧,可能需要-XTypeInType
)。请注意,我们在类型级别使用'Succ
。这是提升类型,已启用-XDataKinds
。我们还需要-XKindSignatures
来启用:: Number
约束。
现在让我们写一个函数。
f :: Tree ('Succ 'Zero) -> String
f _ = "It works!"
此功能仅采用最多一层深度的树。我们可以尝试称之为。
f (Leaf "A") -- It works!
f (Node [Leaf "A"]) -- It works!
f (Node [Node [Leaf "A"]]) -- Type error
如果深度太大,它将在编译时失败。
完整示例(包括编译器扩展):
{-# LANGUAGE GADTs, KindSignatures, DataKinds #-}
data Number = Zero | Succ Number
data Tree (lvl :: Number) where
Leaf :: String -> Tree lvl
Node :: [Tree lvl] -> Tree ('Succ lvl)
f :: Tree ('Succ 'Zero) -> String
f _ = "It works!"
这不是你可以做的一切。肯定会有扩展,但它可以解决问题,并希望能指出正确的方向。
答案 1 :(得分:9)
因此这个问题有很多困难。不过,Peano号码是一个很好的起点:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
data Nat = Z | S Nat
接下来,我们需要某种方式来说一个数字是"更大"比另一个。我们可以通过首先为" n小于或等于m"
定义一个归纳类来实现。class (n :: Nat) <= (m :: Nat)
instance Z <= n
instance n <= m => (S n <= S m)
然后我们可以定义&#34;小于&#34;就此而言:
type n < m = S n <= m
最后,这里是树和级别:
data Tree n where
Leaf :: String -> Tree n
Node :: n < z => Level z -> [Tree z] -> Tree n
data Level n where
Level0 :: Level Z
Level1 :: Level (S Z)
Level2 :: Level (S (S Z))
Level3 :: Level (S (S (S Z)))
Level4 :: Level (S (S (S (S Z))))
并且,根据需要,第一个示例编译:
tree = Node Level1
[ Node Level2 []
, Node Level3 []
]
虽然第二个没有:
tree = Node Level2
[ Node Level1 []
]
为了获得额外的乐趣,我们现在可以添加一个&#34;自定义类型错误&#34; (这需要UndecidableInstances
:
import GHC.TypeLits (TypeError, ErrorMessage(Text))
instance TypeError (Text "Nodes must contain trees of a higher level") => S n < Z
所以当你写:
tree = Node Level2
[ Node Level1 []
]
您将获得以下内容:
•节点必须包含更高级别的树
•表达式:Node Level1 []
在'Node'的第二个参数中,即'[Node Level1 []]'
表达式:Node Level2 [Node Level1 []]
如果你想做&#34;等级&#34;更通用,您还需要更多扩展程序:
{-# LANGUAGE TypeApplications, RankNTypes, AllowAmbiguousTypes, TypeFamilies #-}
import qualified GHC.TypeLits as Lits
data Level n where
Level0 :: Level Z
LevelS :: !(Level n) -> Level (S n)
class HasLevel n where level :: Level n
instance HasLevel Z where level = Level0
instance HasLevel n => HasLevel (S n) where level = LevelS level
type family ToPeano (n :: Lits.Nat) :: Nat where
ToPeano 0 = Z
ToPeano n = S (ToPeano (n Lits.- 1))
node :: forall q z n m. (ToPeano q ~ z, HasLevel z, n < z) => [Tree z] -> Tree n
node = Node level
tree =
node @1
[ node @2 []
, node @3 []
]