我正在学习Haskell,并编写一个简短的解析脚本作为练习.我的大部分脚本都包含纯函数,但我有两个嵌套的IO组件:
我的工作原理,但嵌套的IO和fmap图层"感觉"笨重,就像我应该避免嵌套IO(不知何故),或者更巧妙地使用do notation来避免所有的fmaps.我想知道我是否过于复杂,做错了等等.以下是一些相关的代码:
getPaths :: FilePath -> IO [String]
getPaths folder = do
allFiles <- listDirectory folder
let txtFiles = filter (isInfixOf ".txt") allFiles
paths = map ((folder ++ "/") ++) txtFiles
return paths
getConfig :: FilePath -> IO [String]
getConfig path = do
config <- readFile path
return $ lines config
main = do
paths = getPaths "./configs"
let flatConfigs = map getConfigs paths
blockConfigs = map (fmap chunk) flatConfigs
-- Parse and do stuff with config data.
return
Run Code Online (Sandbox Code Playgroud)
我最终处理IO [IO String]
使用listDirectory作为readFile的输入.不是难以管理的,但是如果我使用do notation来解开[IO String]
发送到一些解析器函数,我仍然最终使用嵌套fmap
或污染我所谓的具有IO感知的纯函数(fmap等).后者似乎更糟糕,所以我正在做前者.例:
type Block = [String]
getTrunkBlocks :: [Block] -> [Block]
getTrunkBlocks = filter (liftM2 (&&) isInterface isMatchingInt)
where isMatchingInt line = isJust $ find predicate line
predicate = isInfixOf "switchport mode trunk"
main = do
paths <- getPaths "./configs"
let flatConfigs = map getConfig paths
blockConfigs = map (fmap chunk) flatConfigs
trunks = fmap (fmap getTrunkBlocks) blockConfigs
return $ "Trunk count: " ++ show (length trunks)
Run Code Online (Sandbox Code Playgroud)
fmap,fmap,fmap ...我觉得我无意中使这个变得比必要的更复杂,并且无法想象如果我有更深的IO嵌套,这会变得多么复杂.
建议?
提前致谢.
我想你想要这样的东西main
:
main = do
paths <- getPaths "./configs"
flatConfigs <- traverse getConfig paths
let blockConfigs = fmap chunk flatConfigs
-- Parse and do stuff with config data.
return ()
Run Code Online (Sandbox Code Playgroud)
相比
fmap :: Functor f => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)
和
traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
Run Code Online (Sandbox Code Playgroud)
它们非常相似,但traverse
让你使用像IO
.
以下是再次专门用于比较的类型:
fmap :: (a -> b) -> [a] -> [b]
traverse :: (a -> IO b) -> [a] -> IO [b]
Run Code Online (Sandbox Code Playgroud)
(traverse
也称为mapM
)
你对'嵌套'的看法实际上是对monad是什么的非常好的洞察力.Monads可以看作Functors,有两个额外的操作,返回类型a -> m a
和连接类型m (m a) -> m a
.然后我们可以创建a -> m b
可组合类型的函数:
fmap :: (a -> m b) -> m a -> m (m b)
f =<< v = join (fmap f v) :: (a -> m b) -> m a -> m b
Run Code Online (Sandbox Code Playgroud)
所以我们想在这里使用join,但m [m a]
目前我们的monad组合器不会直接帮助.让我们搜索m [m a] -> m (m [a])
使用hoogle,我们的第一个结果看起来很有希望.是的sequence:: [m a] -> m [a]
.
如果我们查看相关函数,我们也会发现traverse :: (a -> IO b) -> [a] -> IO [b]
它们是相似的sequence (fmap f v)
.
有了这些知识,我们可以写:
readConfigFiles path = traverse getConfig =<< getPaths path
Run Code Online (Sandbox Code Playgroud)