Haskell-使用读取器monad的二叉树中每个节点的深度

Haskell-使用读取器monad的二叉树中每个节点的深度,haskell,monads,reader-monad,Haskell,Monads,Reader Monad,我编写了以下代码。它正在工作并使用读卡器monad 你能给我一些关于Haskell中代码风格的提示吗?我主要是指monads,我是新手 import Control.Monad.Reader data Tree a = Node a (Tree a) (Tree a) | Empty renumberM :: Tree a -> Reader Int (Tree Int) renumberM (Node _ l r) = ask >>= (\x -&

我编写了以下代码。它正在工作并使用
读卡器
monad

你能给我一些关于Haskell中代码风格的提示吗?我主要是指monads,我是新手

import Control.Monad.Reader

data Tree a = Node a (Tree a) (Tree a)
            | Empty

renumberM :: Tree a -> Reader Int (Tree Int)
renumberM (Node _ l r) = ask >>= (\x -> 
                         return (Node x (runReader (local (+1) (renumberM l)) x) 
                                        (runReader (local (+1) (renumberM r)) x)))
renumberM Empty = return Empty

renumber'' :: Tree a -> Tree Int
renumber'' t = runReader (renumberM t) 0

不需要像您在这里使用
runReader
那样进出
读取器;相反,您可以将其重写为

renumberR :: Tree a -> Reader Int (Tree Int)
renumberR (Node _ l r) = do
    x <- ask
    l' <- local (+1) (renumberR l)
    r' <- local (+1) (renumberR r)
    return (Node x l' r')
renumberR Empty = return Empty

请注意,我已将您的函数重命名为
重新编号
,以强调它在
读取器
中运行,但不一定使用它的一元接口。

我想向您展示的是,您的想法是一个更一般概念的实例-压缩。这是您的程序的一个版本,它采用了更简单、更实用的风格

应用函子 以下是对以下各项的定义:

pure x
生成一个由
x
s组成的无限树。这很好,因为Haskell是一种懒惰的语言

     +-----x-----+
     |           |
  +--x--+     +--x--+
  |     |     |     |
+-x-+ +-x-+ +-x-+ +-x-+
|   | |   | |   | |   |
          etc
因此,树
t pure x
的形状与
t
的形状相同:只有当遇到
空的
时,才停止遍历,而
pure x
中没有任何树。(这同样适用于
纯x t

这是使数据结构成为
Applicative
实例的常用方法。例如,标准库包括,它与我们的树非常相似:

newtype ZipList a = ZipList { getZipList :: [a] }
instance Applicative ZipList where
    pure x = ZipList (repeat x)
    ZipList fs <*> ZipList xs = ZipList (zipWith ($) fs xs)
标签作为拉链 计划是将输入树与包含每个级别深度的无限树压缩在一起。输出将是一棵与输入树形状相同的树(因为深度树是无限的),但每个节点都将标记其深度

depths :: Tree Integer
depths = go 0
    where go n = let t = go (n+1) in Node n t t
这就是
深度
的样子:

     +-----0-----+
     |           |
  +--1--+     +--1--+
  |     |     |     |
+-2-+ +-2-+ +-2-+ +-2-+
|   | |   | |   | |   |
          etc
现在我们已经设置了所需的结构,为树添加标签就很容易了

labelDepths :: Tree a -> Tree (Integer, a)
labelDepths = zipA depths
按照最初的指定,通过丢弃原始标签来重新标记树

快速测试:

ghci> let myT = Node 'x' (Node 'y' (Node 'z' Empty Empty) (Node 'a' Empty Empty)) (Node 'b' Empty Empty)
ghci> labelDepths myT
Node (0,'x') (Node (1,'y') (Node (2,'z') Empty Empty) (Node (2,'a') Empty Empty)) (Node (1,'b') Empty Empty)

    +--'x'-+                      +--(0,'x')-+
    |      |    labelDepths       |          |
 +-'y'-+  'b'       ~~>      +-(1,'y')-+  (1,'b')
 |     |                     |         |
'z'   'a'                 (2,'z')   (2,'a')
你可以设计不同的标签方案,通过改变你所沿着的树。这里有一个告诉您到达节点的路径:

data Step = L | R
type Path = [Step]
paths :: Tree Path
paths = go []
    where go path = Node path (go (path ++ [L])) (go (path ++ [R]))

         +--------[ ]--------+
         |                   |
    +---[L]---+         +---[R]---+
    |         |         |         |
+-[L,L]-+ +-[L,R]-+ +-[R,L]-+ +-[R,R]-+
|       | |       | |       | |       |
                  etc
(可以使用以下方法减轻对上面
++
调用的低效嵌套。)



随着您继续学习Haskell,当程序是一个更深层概念的示例时,您将更善于发现。设置通用结构,就像我在上面的
Applicative
实例中所做的那样,可以很快在代码重用中获得好处。

这里的
State
monad是否更合适?只是好奇,我不知道;这取决于OP想做什么。上面是对原始代码的直接重构。如果你想让右树在左树之后得到数字,你需要使用
State
。他确实说“它正在工作”,所以我很小心地保留了它当前的语义。@dvaergiller:但是如果你不清楚如何使用
State
进行其他类型的编号,请发一个新问题。坦率地说,在这里传递论点是最明显的方法。如果你改变了每次递归调用的内容,我认为读卡器monad/applicative没有任何意义。也许这个问题会在中找到更好的归宿?
     +-----0-----+
     |           |
  +--1--+     +--1--+
  |     |     |     |
+-2-+ +-2-+ +-2-+ +-2-+
|   | |   | |   | |   |
          etc
labelDepths :: Tree a -> Tree (Integer, a)
labelDepths = zipA depths
relabelDepths :: Tree a -> Tree Integer
relabelDepths t = t *> depths
ghci> let myT = Node 'x' (Node 'y' (Node 'z' Empty Empty) (Node 'a' Empty Empty)) (Node 'b' Empty Empty)
ghci> labelDepths myT
Node (0,'x') (Node (1,'y') (Node (2,'z') Empty Empty) (Node (2,'a') Empty Empty)) (Node (1,'b') Empty Empty)

    +--'x'-+                      +--(0,'x')-+
    |      |    labelDepths       |          |
 +-'y'-+  'b'       ~~>      +-(1,'y')-+  (1,'b')
 |     |                     |         |
'z'   'a'                 (2,'z')   (2,'a')
data Step = L | R
type Path = [Step]
paths :: Tree Path
paths = go []
    where go path = Node path (go (path ++ [L])) (go (path ++ [R]))

         +--------[ ]--------+
         |                   |
    +---[L]---+         +---[R]---+
    |         |         |         |
+-[L,L]-+ +-[L,R]-+ +-[R,L]-+ +-[R,R]-+
|       | |       | |       | |       |
                  etc
labelPath :: Tree a -> Tree (Path, a)
labelPath = zipA paths