Haskell:为树中的每个级别关联多态函数

时间:2014-01-07 21:48:36

标签: haskell tree polymorphism

美好的一天!

我有一个元素树:

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的特定实例之间建立一对一映射?

2 个答案:

答案 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