【问题标题】:Creating polymorphic recursive types in Haskell在 Haskell 中创建多态递归类型
【发布时间】:2010-02-04 13:17:17
【问题描述】:

我正在尝试在 Haskell 中创建 Tree 类型。我使用这个简单的数据构造函数来存储一棵树,其中每个节点可以是空的,可以是包含整数的叶子,也可以是包含整数的节点,其分支到其他两个叶子/节点。这是我得到的:

module Tree ( Tree(Empty, Leaf, Node) ) where

data Tree = Empty
| Leaf Int
| Node Tree Int Tree
deriving(Eq, Ord, Show, Read)

这很好,但我需要使 Tree 类型具有多态性。我试过用'a'简单地替换'Int',但它似乎不起作用。是否有其他系统可以使这些类型具有多态性?

【问题讨论】:

    标签: haskell recursion polymorphism


    【解决方案1】:

    确实,您可以为 Tree 提供类型参数,如 Alexander Poluektov 的示例中所示。够简单!但为什么要停在那里?我们可以享受更多的乐趣。除了具有多态数据的递归结构之外,您还可以使结构在递归本身中具有多态

    首先,抽象出树对自身的引用,就像抽象出对Int 的引用一样,用新参数t 替换递归引用。这给我们留下了这个相当模糊的数据结构:

    data TNode t a = Empty
                   | Leaf a
                   | Node (t a) a (t a)
                   deriving (Eq, Ord, Show, Read)
    

    这里已重命名为TNode,因为它不再是真正的树了;只是一个简单的数据类型。现在,为了恢复原始递归并创建一棵树,我们将TNode 扭曲并将其提供给自身:

    newtype Tree a = Tree (TNode Tree a) deriving (Eq, Ord, Show, Read)
    

    现在我们可以递归地使用这个Tree,但遗憾的是要付出一些额外的冗长,就像这样:

    Tree (Node (Tree Empty) 5 (Tree (Leaf 2)))
    

    那么,除了额外的打字之外,这给了我们什么?简单地说,我们已经将基本树 结构 与其包含的数据以及构造和处理数据的方法分开,从而允许我们编写更多通用函数来处理一个或另一个方面。

    例如,我们可以用额外的数据装饰树,或者将额外的东西拼接到树中,而不会影响任何通用的树函数。假设我们想为树的每一部分命名:

    newtype NameTree a = NameTree (String, TNode NameTree a) deriving (Eq, Ord, Show, Read)
    

    另一方面,我们可以编写通用的树遍历逻辑:

    toList f t = toList' f (f t) []
        where toList' f (Node t1 x t2) xs = toList' f (f t1) (x : toList' f (f t2) xs)
              toList' f (Leaf x) xs = x:xs
              toList' _ Empty xs = xs
    

    给定一个从递归树中提取当前TNode 的函数,我们可以在任何这样的结构上使用它:

    treeToList = toList (\(Tree t) -> t)
    nameTreeToList = toList (\(NameTree (_, t)) -> t)
    

    当然,这可能远远超出了您的预期,但它是对 Haskell 允许(不,鼓励)程序员创建多少多态性和通用代码的一个很好的体验。

    【讨论】:

      【解决方案2】:
      data Tree a = Empty
                 | Leaf a
                 | Node (Tree a) a (Tree a)
      

      【讨论】:

        【解决方案3】:

        将 Int 替换为 a 是正确的开始,但您还需要将每次出现的 Tree 替换为 Tree a(在必要时将其括起来)。 data Tree 部分需要 a 来声明 Tree 有一个称为 a 的类型参数。 Node Tree Int Tree 需要表示子树本身是 Tree a 类型,而不是其他树类型。

        【讨论】:

          【解决方案4】:

          尝试阅读一些关于类型构造函数的内容kind

          如果你有一个依赖于一些类型变量的多态类型,那么你的类型构造函数必须有一个能反映这一点的类型。

          例如,类型构造函数MyBool定义在:

          data MyBool = False | True
          

          是一种*。也就是说,我的类型构造函数MyBool 不带参数来定义类型。如果我写类似:

          data MyMaybe a = Just a | Nothing
          

          那么类型构造函数MyMaybe 有一种类型*->*,即它需要一个“类型参数”来定义一个类型。

          您可以比较类型构造函数类型的工作方式和数据构造函数类型的工作方式。

          数据构造函数True可以是一个自己的MyBool类型的数据值,不带任何参数。但是数据构造函数Just 是一个a -> MyMaybe a 类型的值,它将操作 a 类型的值以创建另一个MyMaybe a 类型的值——例如在这个ghci 会话中:

          > let x = Just 5
          > :t x
          Maybe Int
          > let y = Just
          > :t y
          a -> Maybe a
          

          这或多或少与类型构造函数MyMaybeMyBool 之间的差异相当。鉴于MyBool 具有类型*,您可以拥有类型为MyBool 的值,而无需任何额外的类型参数。但是MyMaybe 本身并不是一个类型——它是一个类型构造函数,它在一个类型上“操作”以创建另一个类型,也就是说,它的类型是* -> *。所以,你不能有MyMaybe类型的东西,但是MyMaybe IntMyMaybe BoolMyMaybe [Int]等类型的东西......

          如果一个类型是多态的,它至少需要是* -> *,但也可以是*->*->*,比如:

           data MyPair a b = Pair a b
          

          MyPair 需要两个类型参数来定义一个类型,如MyPair Int BoolMyPair Int Int 等...

          带回家的信息类似于:由于值构造函数具有类型签名,类型构造函数具有种类签名,当您计划新的数据类型时必须考虑到这一点。

          http://en.wikipedia.org/wiki/Kind_%28type_theory%29

          【讨论】:

            猜你喜欢
            • 2014-08-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-09-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-21
            相关资源
            最近更新 更多