美好的一天!
我有一个元素树:
data Tree a = Node [Tree a]
| Leaf a
我需要到达那棵树的叶子。从根到叶的路径由一系列switch
函数确定。该树中的每个层对应于一个特定的switch
函数,该函数将某些内容作为参数并将索引返回给子树。
class Switchable a where
getIndex :: a -> Int
data SwitchData = forall c. Switchable c => SwitchData c
最终目标是提供一个期望所有必要SwitchData
并返回的函数
树上的叶子。
但我认为没办法
在类型级别强制执行一对一映射。我当前对switch
的实施
函数接受SwitchData
个实例的列表:
switch :: SwitchData -> Tree a -> Tree a
switch (SwitchData c) (Node vec) = vec `V.unsafeIndex` getIndex c
switch _ _ = error "switch: performing switch on a Leaf of context tree"
switchOnData :: [SwitchData] -> Tree a -> a
switchOnData (x:xs) tree = switchOnData xs $ switch x tree
switchOnData [] (Leaf c) = c
switchOnData [] (Node _) = error "switchOnData: partial data"
Switchable
个实例的顺序和确切类型
既不是在编译时也不是在运行时,对于程序员而言,这是正确的,这让我很烦恼。 gist反映了当前的事件状态。
您能否建议一些方法在上下文树中的层与Switchable
的特定实例之间建立一对一映射?
答案 0 :(得分:6)
Your current solution仅相当于switchOnIndices :: [Int] -> Tree a -> a
(只需在存储在列表中之前应用getIndex
!)。明确地说明这一点" partial"通过将回报包裹在Maybe
中,这实际上可能是理想的签名,简单而精细。
但显然,你的真实用例更复杂;你希望每个级别都有基本不同的词典。然后,您实际上需要将多个类型的类型链接到树深度。你正在寻找一些疯狂的近乎依赖型的hackery!
{-# LANGUAGE GADTs, TypeOperators, LambdaCase #-}
infixr 5 :&
data Nil = Nil
data s :& ss where
(:&) :: s -> ss -> s :& ss
data Tree switch a where
Node ::
(s -> Tree ss a) -- Such a function is equiv. to `Switchable c => (c, [Tree a])`.
-> Tree (s :& ss) a
Leaf :: a -> Tree Nil a
switch :: s -> Tree (s :& ss) a -> Tree ss a
switch s (Node f) = f s
switchOnData :: s -> Tree s a -> a
switchOnData sw (Node f) = switchOnData ss $ f s
where (s :& ss) = sw
switchOnData _ (Leaf a) = a
data Sign = P | M
signTree :: Tree (Sign :& Sign :& Nil) Char
signTree = Node $ \case P -> Node $ \case P -> Leaf 'a'
M -> Leaf 'b'
M -> Node $ \case P -> Leaf 'c'
M -> Leaf 'd'
testSwitch :: IO()
testSwitch = print $ switchOnData (P :& M :& Nil) signTree
main = testSwitch
当然,这极大地限制了树结构的灵活性:每个级别具有固定的预定数量的节点。事实上,这使得整个结构例如signTree
仅相当于(Sign, Sign) -> Char
,因此,除非您因某些特定原因(例如附加到节点的额外信息)确实需要树,否则为什么不使用它呢!
或者,再次,简单[Int] -> Tree a -> a
签名的方式。但是使用存在感对我来说毫无意义。
答案 1 :(得分:2)
假设我们在某个特定Tree
遍历Node
,并且还可以使用我们特定类型的所需多态Switchable
值。换句话说,我们想要step
step :: Switchable -> Tree a -> Maybe (Tree a)
此处输出Tree
是输入Tree
的子项之一,我们将其包装在Maybe
中以防万一出错。所以我们试着写step
step s Leaf{} = Nothing
step s (Node children) = switch s (?extract children)
在这里,挑战源于定义?extract
,因为我们需要它来多态地为switch s
生成正确的值以进行操作。我们无法有意义地使用多态来实现这一点:
-- won't work!
class Extractable b where
extract :: [Tree a] -> b
因为现在switch . extract
含糊不清。实际上,如果Switchable
通过存在量化其需要传递的值来操作,则无法(在Typeable
之外)构建extract
工作正常。相反,我们需要每个Switchable
都有自己的个人extract
,其类型正确,这种类型只存在于数据类型的上下文中。
{-# LANGUAGE ExistentialQuantification #-}
data Switchable = forall pass . Switchable
{ extract :: [Tree a] -> pass
, switch :: pass -> Int
}
step s (Node children) = children `safeLookup` switch s (extract s children)
但是由于包含了这种存在类型,我们知道绝对没有办法使用Switchable
除了立即计算存在隐藏pass
值然后用{{1}消耗它}。甚至没有任何理由存储switch
,因为我们可以从中获取任何信息的唯一方法是使用pass
。
总而言之,这导致了switch
应该是这样的直觉。
Switchable
甚至
data Switchable = Switchable { switch :: [Tree a] -> Int }
step s (Node children) = children `safeLookup` switch s children