举个简单的例子,如果我们有类似的东西
names = ["A", "B", "C", "D", "E", "F", "G"]
ageTree =
Node 1 [
Node 2 [
Node 3 []
],
Node 4 [
Node 5 [],
Node 6 []
],
Node 7 []
]
然后我们希望 buildPersonTree names ageTree 输出类似
Node (Person 1 "A") [
Node (Person 2 "B") [
Node (Person 3 "C") []
],
Node (Person 4 "D") [
Node (Person 5 "E") [],
Node (Person 6 "F") []
],
Node (Person 7 "G") []
]
这可以通过多种方式完成,直接递归就是其中之一,但这可能会变得很棘手,因为您需要在下一个分支之前完全遍历一个分支,同时跟上第一个分支上分配的名称。相反,我们可以使用 state monad 使这几乎变得微不足道,即使效率稍低:
import Data.Tree
import Data.Maybe (listToMaybe)
import Control.Applicative
import Control.Monad.State
data PersonNode = PersonNode { age :: Int, name :: String } deriving (Eq, Show)
mkPerson :: (Functor m, MonadState [String] m) => Int -> m (Maybe PersonNode)
mkPerson age' = do
-- name' :: Maybe String
name' <- listToMaybe <$> get
-- Remove that name from the head of the list of names
modify (drop 1)
-- fmap PersonNode over our Maybe String in name'
return $ PersonNode age' <$> name'
buildTree :: (Functor m, MonadState [String] m) => Tree Int -> m (Maybe (Tree PersonNode))
buildTree (Node age' children) = do
-- Get the root PersonNode using mkPerson
root <- mkPerson age'
-- children' :: [Maybe (Tree PersonNode)]
children' <- mapM buildTree children
-- Applicative combinators make error handling simple
return $ Node <$> root <*> sequence children'
main :: IO ()
main = putStrLn
$ maybe "Not enough names" (drawTree . fmap show)
$ evalState (buildTree testAgeTree) testNames
testAgeTree :: Tree Int
testAgeTree = Node 1 [Node 2 [Node 3 []], Node 4 [Node 5 [], Node 6 []], Node 7 []]
testNames :: [String]
testNames = ["A", "B", "C", "D", "E", "F", "G"]
我确保使用Maybe 表示无法从列表中获取名称,这使事情变得有点复杂,但除此之外,Haskell 允许我们使用非常简单的递归来构建子节点, monad 组合器 mapM 和 sequence 使这变得非常简单。 applicative 组合器还使错误处理几乎透明,我从来不用提及Just 或Nothing。