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