如何编程组合Reified Lenses?Haskell lens库实践疑问
解决方案:用Lens库编程生成Setter列表并实现列表转完全二叉树
首先,我们先解决你遇到的两个核心问题,再给出完整的实现方案。
问题1:映射Reified Setter构造器报错
fmap Setter [_1, _1]报错的原因是_1(或你自定义的_left/_right Lens)的类型是多态的,Haskell无法自动推断出fmap后的列表元素类型。解决方法是给列表添加显式类型注解,统一元素的类型:
import Control.Lens import Control.Lens.Reified -- 假设我们已经定义了Tree的左、右子树Lens _left :: Lens' (Tree a) (Tree a) _left f (Node x l r) = (\newL -> Node x newL r) <$> f l _left _ Nil = pure Nil _right :: Lens' (Tree a) (Tree a) _right f (Node x l r) = (\newR -> Node x l newR) <$> f r _right _ Nil = pure Nil -- 正确的映射写法:添加类型注解统一Setter类型 depth2Setters :: [Setter (Tree a) (Tree a) (Tree a) (Tree a)] depth2Setters = fmap Setter [_left, _right]
这样fmap Setter就能正常工作,因为Haskell可以明确推断出每个Lens要转换成的Setter类型。
问题2:组合Reified Setter
Reified的Setter类型实现了Category类型类,因此可以直接用(.)运算符组合多个Setter。比如,要组合左子树和右子树的Setter,得到指向"左子树的右子树"的Setter:
leftSetter :: Setter (Tree a) (Tree a) (Tree a) (Tree a) leftSetter = Setter _left rightSetter :: Setter (Tree a) (Tree a) (Tree a) (Tree a) rightSetter = Setter _right -- 组合后的Setter,等价于 Setter (_left . _right) leftRightSetter :: Setter (Tree a) (Tree a) (Tree a) (Tree a) leftRightSetter = leftSetter . rightSetter
你也可以直接组合Lens后再包裹Setter构造器,效果完全一致:Setter (_left . _right)。
完整实现:编程生成指定深度的Setter列表并转完全二叉树
1. 基础定义
首先定义Tree类型、访问左右子树的Lens,以及路径方向类型:
import Control.Lens import Control.Lens.Reified import Control.Monad.State import Control.Monad (replicateM) data Tree a = Nil | Node a (Tree a) (Tree a) deriving (Show) -- 访问左子树的Lens _left :: Lens' (Tree a) (Tree a) _left f (Node x l r) = (\newL -> Node x newL r) <$> f l _left _ Nil = pure Nil -- 访问右子树的Lens _right :: Lens' (Tree a) (Tree a) _right f (Node x l r) = (\newR -> Node x l newR) <$> f r _right _ Nil = pure Nil -- 表示路径方向:左/右 data Dir = L | R deriving (Show)
2. 生成指定深度的Setter列表
我们通过生成"路径"的方式来构造Setter:每个深度的节点对应一条从根到该节点的路径(比如深度3的第一个节点路径是[L, L]),再将路径转换成对应的Setter。
-- 将方向转换为对应的Lens dirToLens :: Dir -> Lens' (Tree a) (Tree a) dirToLens L = _left dirToLens R = _right -- 将路径转换为Reified Setter pathToSetter :: [Dir] -> Setter (Tree a) (Tree a) (Tree a) (Tree a) pathToSetter = Setter . foldr (.) id . map dirToLens -- 生成指定深度的所有路径:深度d对应长度为d-1的方向组合 generatePaths :: Int -> [[Dir]] generatePaths 1 = [[]] -- 深度1对应根节点,路径为空 generatePaths n = replicateM (n-1) [L, R] -- 生成指定深度的Setter列表 generateSetters :: Int -> [Setter (Tree a) (Tree a) (Tree a) (Tree a)] generateSetters = map pathToSetter . generatePaths
3. 用State Monad实现列表转完全二叉树
我们用State维护当前的树和剩余的输入元素,逐层用Setter设置节点:
-- 处理单个深度的节点:用Setter列表将元素设置到树的对应位置 processLayer :: [Setter (Tree a) (Tree a) (Tree a) (Tree a)] -> State (Tree a, [a]) () processLayer setters = do (tree, xs) <- get let (layerElems, restElems) = splitAt (length setters) xs -- 用Setter将对应位置的Nil替换为Node x Nil Nil updateTree :: Setter (Tree a) (Tree a) (Tree a) (Tree a) -> a -> Tree a -> Tree a updateTree setter x = over (runSetter setter) (const $ Node x Nil Nil) newTree = foldr (\(s, x) t -> updateTree s x t) tree (zip setters layerElems) put (newTree, restElems) -- 计算完全二叉树的最大深度 maxDepth :: [a] -> Int maxDepth [] = 0 maxDepth xs = floor (logBase 2 (fromIntegral (length xs))) + 1 -- 主函数:将列表转换为完全二叉树 listToTree :: [a] -> Tree a listToTree [] = Nil listToTree xs = evalState buildTree (Nil, xs) where buildTree = do let depth = maxDepth xs -- 从深度1到最大深度逐层处理 mapM_ (\d -> processLayer (generateSetters d)) [1..depth] (finalTree, _) <- get return finalTree
测试示例
main :: IO () main = print $ listToTree [1,2,3,4,5,6,7] -- 输出:Node 1 (Node 2 (Node 4 Nil Nil) (Node 5 Nil Nil)) (Node 3 (Node 6 Nil Nil) (Node 7 Nil Nil))
内容的提问来源于stack exchange,提问作者user821596
相关产品推荐
相关产品推荐

