考虑以下数据结构,表示级别增加但不一定是连续的树:
data MyTree (n :: T) where
MyLeaf :: MyTree n
MyNode :: Plus n m z => [MyTree ('Succ z)] -> MyTree n
其中T
表示类型级别的Peano数字,定义为
class Plus (n :: T) (m :: T) (r :: T) | r n -> m
instance Plus 'Zero m m
instance Plus n m r => Plus ('Succ n) m ('Succ r)
构建像
这样的树很容易myTreeOne :: MyTree ('Succ 'Zero)
myTreeOne = MyNode ([ MyLeaf ] :: [MyTree ('Succ ('Succ 'Zero))])
myTree :: MyTree 'Zero
myTree = MyNode [ MyLeaf, myTreeOne ]
或
myLeafTwo :: MyTree ('Succ ('Succ 'Zero))
myLeafTwo = MyLeaf
myOtherTree :: MyTree 'Zero
myOtherTree = MyNode [ myLeafTwo ]
现在我想定义以下功能:
myTreeComponents MyLeaf = []
myTreeComponents (MyNode components) = components
只提取树的直接子节点列表,但我无法写出正确的类型。
这是我得到的错误
• Couldn't match expected type ‘p’ │
with actual type ‘[MyTree ('Succ z)]’ │
because type variable ‘z’ would escape its scope │
This (rigid, skolem) type variable is bound by │
a pattern with constructor: │
MyNode :: forall (n :: T) (m :: T) (z :: T). │
Plus n m z => │
[MyTree ('Succ z)] -> MyTree n, │
in an equation for ‘myTreeComponents’ │
at src/Model.hs:159:19-35 │
• In the expression: components │
In an equation for ‘myTreeComponents’: │
myTreeComponents (MyNode components) = components │
• Relevant bindings include │
components :: [MyTree ('Succ z)] (bound at src/Model.hs:159:26) │
myTreeComponents :: MyTree n -> p (bound at src/Model.hs:158:1) │
| │
159 | myTreeComponents (MyNode components) = components │
| ^^^^^^^^^^
对于依赖类型语言,它应该类似于
exists m. Plus n m z => MyTree n -> [ MyTree ('Succ z) ]
是否可以在Haskell中编写这样的类型?否则我该怎么写我的功能?
答案 0 :(得分:8)
这是对您的代码的修改,添加了Proxy
,因此要记住"号码m
。
{-# LANGUAGE GADTs, KindSignatures, DataKinds, TypeFamilies,
MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, UndecidableInstances #-}
{-# OPTIONS -Wall #-}
import Data.Proxy
data T = Zero | Succ T
class Plus (n :: T) (m :: T) (z :: T) | n m -> z where
instance Plus n 'Zero n
instance Plus n m z => Plus n ('Succ m) ('Succ z)
data MyTree (n :: T) where
MyLeaf :: MyTree n
MyNode :: Plus n m z => ! (Proxy m) -> [MyTree ('Succ z)] -> MyTree n
myTreeOne :: MyTree ('Succ 'Zero)
myTreeOne = MyNode (Proxy :: Proxy 'Zero) ([ MyLeaf ] :: [MyTree ('Succ ('Succ 'Zero))])
myTree :: MyTree 'Zero
myTree = MyNode (Proxy :: Proxy 'Zero) [ MyLeaf, myTreeOne ]
myLeafTwo :: MyTree ('Succ ('Succ 'Zero))
myLeafTwo = MyLeaf
myOtherTree :: MyTree 'Zero
myOtherTree = MyNode (Proxy :: Proxy ('Succ 'Zero)) [ myLeafTwo ]
为了能够编写最终函数myTreeComponents
,我们需要一个自定义存在类型:
data Nodes (n :: T) where
Nodes :: Plus n m z => ! (Proxy m) -> [MyTree ('Succ z)] -> Nodes n
这基本上是MyTree
,只有第二个构造函数。
最后,模式匹配现在就足够了。
myTreeComponents :: MyTree n -> Nodes n
myTreeComponents MyLeaf = Nodes (Proxy :: Proxy 'Zero) []
myTreeComponents (MyNode p components) = Nodes p components
答案 1 :(得分:4)
您通常可以使用CPS对存在物进行编码。
exists a. f a
可以表示为
(forall a. f a -> r) -> r
但是,我不认为你的
exists m. Plus n m z => MyTree n -> [ MyTree ('Succ z) ]
是您想要的类型。首先,存在位置错误 - 不存在单个全局类型m
,而是每个MyTree n
存在可能不同的m
。
MyTree n -> exists m. Plus n m z => [ MyTree ('Succ z) ]
此处调用者可以选择z
,并且,如果有n + m = z
的证据,则可以提取子列表。这是一致的,但这样的证据可能很难得到。我认为你真的想要一个双重存在:
MyTree n -> exists m z. Plus n m z & [ MyTree ('Succ z) ]
并且我使用&
作为=>
的双重身份,这是一种包含字典而不是将其作为参数的类型。
type a & b = (Dict a, b)
所以这就是说,对于n
级别的任何树,有一些z >= n
(通过添加m
见证),这样就会有一个级别{{1 }}。是的,我认为是对的。现在让我们的CPS将其编码为:
'Succ z
答案 2 :(得分:4)
我喜欢chi的修复,因为使用Proxy
来存储类型是一个很好的技巧。在他的回答中,Proxy
用于在调用m
构造函数时消除Plus n m z
约束中Nodes
使用的歧义。 (此时范围内有Plus n m z
约束,但没有代理,就无法告诉编译器选择它。)
除了Proxy
之外,还有另一种修复歧义类型的方法:消除模糊类型。在这种情况下,那是m
。因此,我们将拥有一个Plus n m z
类,而不是LE n z
类,它具有所有相同的实例,但在任何地方都没有提到m
。所以:
{-# LANGUAGE GADTs, KindSignatures, DataKinds, TypeFamilies,
MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, UndecidableInstances, ScopedTypeVariables #-}
{-# OPTIONS -Wall #-}
data T = Zero | Succ T
class LE (n :: T) (z :: T)
instance LE 'Zero n
instance LE n z => LE ('Succ n) ('Succ z)
有一个皱纹。在chi的回答中,他使用了一个略微修改的Plus
定义,它可以免费提供Plus n 'Zero n
个实例,但是在你的配方中没有这个实例,因此LE n n
不是免费提供的。所以我们必须稍微装饰我们的叶子。
data MyTree (n :: T) where
MyLeaf :: LE n n => MyTree n
MyNode :: LE n z => [MyTree ('Succ z)] -> MyTree n
所有混凝土树都和以前一样(不需要额外的Proxy
)。
myTreeOne :: MyTree ('Succ 'Zero)
myTreeOne = MyNode [ MyLeaf :: MyTree ('Succ ('Succ 'Zero)) ]
myTree :: MyTree 'Zero
myTree = MyNode [ MyLeaf, myTreeOne ]
myLeafTwo :: MyTree ('Succ ('Succ 'Zero))
myLeafTwo = MyLeaf
myOtherTree :: MyTree 'Zero
myOtherTree = MyNode [ myLeafTwo ]
myTreeComponents
与chi的答案(除了删除Proxy
)之间的唯一区别是我们需要将一个类型变量放入范围以供在体内使用。
data Nodes (n :: T) where
Nodes :: LE n z => [MyTree ('Succ z)] -> Nodes n
myTreeComponents :: forall n. MyTree n -> Nodes n
myTreeComponents MyLeaf = Nodes ([] :: [MyTree ('Succ n)])
myTreeComponents (MyNode components) = Nodes components
答案 3 :(得分:-1)
类似的东西(我不知道这种类型是否允许,现在无法检查)
MyNode n -> forall z .Plus n m z => [MyTree ('Succ z)]
z
类型未公开,因此可以是任何类型。如果z
的{{1}}类型被公开,那么调用者可以为它指定一个具体的类型,它一定不能这样做